package com.cloudweb.oa.service.impl;

import cn.js.fan.db.ListResult;
import cn.js.fan.db.ResultIterator;
import cn.js.fan.db.ResultRecord;
import cn.js.fan.db.SQLFilter;
import cn.js.fan.mail.SendMail;
import cn.js.fan.security.ThreeDesUtil;
import cn.js.fan.util.*;
import cn.js.fan.util.file.FileUtil;
import cn.js.fan.web.Global;
import cn.js.fan.web.SkinUtil;
import com.alibaba.fastjson.JSONObject;
import com.cloudweb.oa.bean.ExportExcelItem;
import com.cloudweb.oa.cache.DepartmentCache;
import com.cloudweb.oa.cache.ExportExcelCache;
import com.cloudweb.oa.cache.FlowFormDaoCache;
import com.cloudweb.oa.cache.UserCache;
import com.cloudweb.oa.entity.Department;
import com.cloudweb.oa.entity.DeptUser;
import com.cloudweb.oa.entity.User;
import com.cloudweb.oa.exception.HackException;
import com.cloudweb.oa.security.AuthUtil;
import com.cloudweb.oa.service.*;
import com.cloudweb.oa.utils.*;
import com.cloudwebsoft.framework.db.JdbcTemplate;
import com.cloudwebsoft.framework.util.IPUtil;
import com.cloudwebsoft.framework.util.LogUtil;
import com.redmoon.kit.util.FileUpload;
import com.redmoon.oa.Config;
import com.redmoon.oa.LogDb;
import com.redmoon.oa.base.IAttachment;
import com.redmoon.oa.db.SequenceManager;
import com.redmoon.oa.dept.DeptDb;
import com.redmoon.oa.dept.DeptMgr;
import com.redmoon.oa.dept.DeptUserDb;
import com.redmoon.oa.flow.*;
import com.redmoon.oa.flow.FormDAO;
import com.redmoon.oa.flow.macroctl.MacroCtlMgr;
import com.redmoon.oa.flow.macroctl.MacroCtlUnit;
import com.redmoon.oa.kernel.License;
import com.redmoon.oa.message.MessageDb;
import com.redmoon.oa.oacalendar.OACalendarDb;
import com.redmoon.oa.person.PlanDb;
import com.redmoon.oa.person.UserDb;
import com.redmoon.oa.person.UserMgr;
import com.redmoon.oa.person.UserSetupDb;
import com.redmoon.oa.pvg.Privilege;
import com.redmoon.oa.pvg.RoleDb;
import com.redmoon.oa.shell.BSHShell;
import com.redmoon.oa.sys.DebugUtil;
import com.redmoon.oa.ui.LocalUtil;
import com.redmoon.oa.util.*;
import com.redmoon.oa.visual.*;
import com.redmoon.oa.flow.Attachment;
import jxl.Workbook;
import jxl.WorkbookSettings;
import jxl.write.Label;
import jxl.write.WritableSheet;
import jxl.write.WritableWorkbook;
import jxl.write.WriteException;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.mail.internet.MimeUtility;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.sql.SQLException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Service
public class WorkflowServiceImpl implements WorkflowService {
    @Autowired
    I18nUtil i18nUtil;

    @Autowired
    HttpServletRequest request;

    @Autowired
    UserCache userCache;

    @Autowired
    SysUtil sysUtil;

    @Autowired
    AuthUtil authUtil;

    @Autowired
    ConfigUtil configUtil;

    @Autowired
    ExportExcelCache exportExcelCache;

    @Autowired
    SysProperties sysProperties;

    @Autowired
    WorkflowUrgeService workflowUrgeService;

    @Autowired
    IDeptUserService deptUserService;

    @Autowired
    DepartmentCache departmentCache;

    @Autowired
    IFileService fileService;

    @Autowired
    IRoleService roleService;

    @Autowired
    FlowFormDaoCache flowFormDaoCache;

    @Autowired
    private TransactionTemplate transactionTemplate;

    @Value("${transaction.flow.enabled}")
    private boolean transactionEnabled;

    @Autowired
    private ThreadContext threadContext;

    @Autowired
    private IUserService userService;

    @Autowired
    private LogService logService;

    @Override
    public JSONObject finishActionConditional(HttpServletRequest request, Privilege privilege) throws ErrMsgException {
        if (transactionEnabled) {
            // 使用 execute 方法执行有返回值的事务操作
            return transactionTemplate.execute(status -> {
                try {
                    // 打印事务状态
                    LogUtil.getLog(getClass()).info("Is new transaction? " + status.isNewTransaction()); // 应为true
                    return finishAction(request, privilege);
                } catch (Exception e) {
                    // 手动标记事务回滚
                    status.setRollbackOnly();
                    if (e instanceof ErrMsgException) {
                        LogUtil.getLog(getClass()).error("finishActionConditional:" + e.getMessage());
                    } else if (e instanceof HackException) {
                        logService.log(authUtil.getUserName(), IPUtil.getRemoteAddr(SpringUtil.getRequest()), LogDb.TYPE_HACK, e.getMessage());
                    } else {
                        LogUtil.getLog(getClass()).error(e);
                    }
                    // 置为异常状态
                    threadContext.setAbnormal(true);
                    throw new ErrMsgException(e.getMessage());
                }
            });
        } else {
            return finishAction(request, privilege);
        }
    }

    // 20230522 因为上传文件时间有可能超长，故注释掉事务
    // @Transactional(rollbackFor = {Exception.class, RuntimeException.class})
    @Override
    public JSONObject finishAction(HttpServletRequest request, Privilege privilege) throws ErrMsgException {
        boolean re;
        WorkflowMgr wfm = new WorkflowMgr();
        JSONObject json = new JSONObject();

        try {
            wfm.doUpload(request.getServletContext(), request);
        } catch (ErrMsgException e) {
            // 可能会抛出验证非法的错误信息
            LogUtil.getLog(getClass()).error(e);
            DebugUtil.e(getClass(), "doUpload", e.getMessage());

            json.put("ret", "0");
            json.put("msg", e.getMessage());
            json.put("op", "");
            return json;
        }

        request.setAttribute("workflowParams", new WorkflowParams(request, wfm.getFileUpload()));

        String op = wfm.getFieldValue("op");
        String strFlowId = wfm.getFieldValue("flowId");
        int flowId = Integer.parseInt(strFlowId);
        String strActionId = wfm.getFieldValue("actionId");
        int actionId = Integer.parseInt(strActionId);
        String strMyActionId = wfm.getFieldValue("myActionId");
        long myActionId = Long.parseLong(strMyActionId);

        WorkflowDb wf = wfm.getWorkflowDb(flowId);

        WorkflowActionDb wa = new WorkflowActionDb();
        wa = wa.getWorkflowActionDb(actionId);
        if (!wa.isLoaded()) {
            // out.print(SkinUtil.makeErrMsg(request, "没有正在办理的节点！"));
            String str = LocalUtil.LoadString(request, "res.flow.Flow", "notBeingHandle");
            json.put("ret", "0");
            json.put("op", op);
            json.put("msg", str);
            return json;
        }

        MyActionDb myActionDb = new MyActionDb();
        myActionDb = myActionDb.getMyActionDb(myActionId);
        if (myActionDb.getCheckStatus() == MyActionDb.CHECK_STATUS_PASS) {
            json.put("ret", "0");
            json.put("op", op);
            String str = LocalUtil.LoadString(request, "res.flow.Flow", "noNeedToDealWith");
            json.put("msg", str);
            return json;
        } else if (myActionDb.getCheckStatus() == MyActionDb.CHECK_STATUS_PASS_BY_RETURN) {
            json.put("ret", "0");
            json.put("op", op);
            String str = LocalUtil.LoadString(request, "res.flow.Flow", "upcomingProcess");
            json.put("msg", str);
            return json;
        }

        String result = wfm.getFieldValue("cwsWorkflowResult");
        myActionDb.setResult(result);
        /*
        // 如果FinishAction在处理时抛出了异常，则不能置状态为checked，否则回到待办记录列表后，会找不到此记录
        if(op!= null && !op.trim().equals("saveformvalue") && !op.trim().equals("saveformvalueBeforeXorCondSelect")){
             myActionDb.setChecked(true);
        }
        */

        // 此处记录，以便于在MyActionDb.isUserAccessedBefore中判断，否则用户之前处理过则跳过时判断用户之前处理时，不准确，因为用户可以会被只需其中一人处理给忽略掉，所以还需改进，如被忽略，则待办记录的状态得变成被忽略状态
        myActionDb.setChecker(authUtil.getUserName());
        myActionDb.save();

        // 退回
        if ("return".equals(op)) {
            re = wfm.ReturnAction(request, wf, wa, myActionId);
            if (re) {
                json.put("ret", "1");
                json.put("op", op);
                String str = LocalUtil.LoadString(request, "res.common", "info_op_success");
                json.put("msg", str);
            } else {
                json.put("ret", "0");
                String str = LocalUtil.LoadString(request, "res.common", "info_op_fail");
                json.put("msg", str);
                json.put("op", op);
            }
            return json;
        }

        if ("finish".equals(op) || "AutoSaveArchiveNodeCommit".equals(op)) {
            try {
                wfm.checkLock(request, wf);
            } catch (ErrMsgException e1) {
                myActionDb.setChecked(false);
                myActionDb.save();
                json.put("ret", "0");
                json.put("msg", e1.getMessage());
                json.put("op", op);
                return json;
            }
            re = wfm.FinishAction(request, wf, wa, myActionId);
            if (re) {
                // 不能在此处自动存档，因此时事务尚未提交，如果在结束事件中修改了字段（如活动举办的标志位），那么读取的数据仍为未修改的状态
                // 会致台帐列表中仍显示为“未举办”，故改在WorkflowController.finishAction中自动存档
                if ("AutoSaveArchiveNodeCommit".equals(op)) {
                    json.put("operate", "AutoSaveArchiveNodeCommit");
                }

                // 如果后继节点中有一个节点是由本人继续处理，且已处于激活状态，则继续处理这个节点
                MyActionDb mad = wa.getNextActionDoingWillBeCheckedByUserSelf(privilege.getUser(request));

                op = "finish";

                if (mad != null) {
                    json.put("ret", "1");
                    json.put("op", op);

                    // 用于在WorkflowController.finishAction中自动存档
                    json.put("flowId", wf.getId());
                    json.put("actionId", wa.getId());

                    json.put("nextMyActionId", "" + mad.getId());
                    String str = i18nUtil.get("clickOk");
                    json.put("msg", str);
                    return json;
                } else {
                    json.put("ret", "1");
                    json.put("op", op);

                    // 用于在WorkflowController.finishAction中自动存档
                    json.put("flowId", wf.getId());
                    json.put("actionId", wa.getId());

                    json.put("nextMyActionId", "");
                    String str = LocalUtil.LoadString(request, "res.common", "info_op_success");
                    json.put("msg", str);
                    return json;
                }
            } else {
                json.put("ret", "0");
                String str = LocalUtil.LoadString(request, "res.common", "info_op_fail");
                json.put("msg", str);
                json.put("op", op);
                return json;
            }
        }
        if ("read".equals(op)) {
            re = wfm.read(request, actionId, myActionId);
            if (re) {
                json.put("ret", "1");
                json.put("op", op);
                String str = LocalUtil.LoadString(request, "res.common", "info_op_success");
                json.put("msg", str);
            } else {
                json.put("ret", "0");
                String str = LocalUtil.LoadString(request, "res.common", "info_op_fail");
                json.put("msg", str);
                json.put("op", op);
            }
            return json;
        }
        if ("manualFinish".equals(op) || "AutoSaveArchiveNodeManualFinish".equals(op) || "manualFinishAgree".equals(op)) {
            re = wfm.saveFormValue(request, wf, wa);
            if (re) {
                re = wfm.ManualFinish(request, flowId, myActionId);
            }
            if (re) {
                json.put("ret", "1");
                json.put("op", op);
                String str = LocalUtil.LoadString(request, "res.common", "info_op_success");
                json.put("msg", str);
            } else {
                // out.print(StrUtil.Alert_Back("操作失败！"));
                json.put("ret", "0");
                String str = LocalUtil.LoadString(request, "res.common", "info_op_fail");
                json.put("msg", str);
                json.put("op", op);
            }
            return json;
        }

        // 自动存档前先保存数据，然后获取flow_displose.jsp中iframe中的report表单数据在 办理完毕 时存档
        if ("editFormValue".equals(op) || "saveformvalue".equals(op) || "saveformvalueBeforeXorCondSelect".equals(op)) {
            // 2013-06-29 fgf 注意保存草稿已经不再进行有效性验证
            re = wfm.saveFormValue(request, wf, wa);
            if (re) {
                json.put("ret", "1");
                json.put("op", op);
                String str = i18nUtil.get("info_op_success");
                json.put("msg", str);
            } else {
                json.put("ret", "0");
                String str = i18nUtil.get("info_op_fail");
                json.put("msg", str);
                json.put("op", op);
            }
        }

        return json;
    }

    @Override
    public JSONObject finishActionByMobileConditional(HttpServletRequest request, Privilege privilege) throws ErrMsgException {
        if (transactionEnabled) {
            // 使用 execute 方法执行有返回值的事务操作
            return transactionTemplate.execute(status -> {
                try {
                    return finishActionByMobile(request, privilege);
                } catch (Exception e) {
                    // 手动标记事务回滚
                    status.setRollbackOnly();
                    // LogUtil.getLog(getClass()).error("finishActionByMobileConditional:" + e.getMessage());
                    if (e instanceof HackException) {
                        logService.log(authUtil.getUserName(), IPUtil.getRemoteAddr(SpringUtil.getRequest()), LogDb.TYPE_HACK, e.getMessage());
                    }
                    // 置为异常状态
                    threadContext.setAbnormal(true);
                    LogUtil.getLog(getClass()).error(e);
                    throw new ErrMsgException(e.getMessage());
                }
            });
        } else {
            return finishActionByMobile(request, privilege);
        }
    }

    @Override
    // @Transactional(rollbackFor = {Exception.class, RuntimeException.class})
    public JSONObject finishActionByMobile(HttpServletRequest request, Privilege privilege) throws ErrMsgException {
        boolean re = false;
        WorkflowMgr wfm = new WorkflowMgr();
        com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
        try {
            wfm.doUpload(request.getServletContext(), request);
        } catch (ErrMsgException e) {
            // 可能会抛出验证非法的错误信息
            LogUtil.getLog(getClass()).error(e);
            DebugUtil.e(getClass(), "doUpload", e.getMessage());

            json.put("ret", "-1");
            json.put("msg", e.getMessage());
            json.put("op", "");
            return json;
        }

        request.setAttribute("workflowParams", new WorkflowParams(request, wfm.getFileUpload()));

        String skey = wfm.getFieldValue("skey");
        com.redmoon.oa.android.Privilege prl = new com.redmoon.oa.android.Privilege();
        prl.doLogin(request, skey);

        String op = wfm.getFieldValue("op");
        String strFlowId = wfm.getFieldValue("flowId");
        int flowId = Integer.parseInt(strFlowId);
        String strActionId = wfm.getFieldValue("actionId");
        int actionId = Integer.parseInt(strActionId);
        String strMyActionId = wfm.getFieldValue("myActionId");
        long myActionId = Long.parseLong(strMyActionId);

        WorkflowDb wf = wfm.getWorkflowDb(flowId);

        String myname = privilege.getUser(request);

        WorkflowActionDb wa = new WorkflowActionDb();
        wa = wa.getWorkflowActionDb(actionId);
        if (!wa.isLoaded()) {
            json.put("res", "-1");
            json.put("msg", "没有正在办理的节点！");
            return json;
        }

        MyActionDb myActionDb = new MyActionDb();
        myActionDb = myActionDb.getMyActionDb(myActionId);

        if (myActionDb.getCheckStatus() == MyActionDb.CHECK_STATUS_PASS) {
            json.put("ret", "-1");
            json.put("op", op);
            String str = LocalUtil.LoadString(request, "res.flow.Flow", "noNeedToDealWith");
            json.put("msg", str);
            return json;
        } else if (myActionDb.getCheckStatus() == MyActionDb.CHECK_STATUS_PASS_BY_RETURN) {
            json.put("ret", "-1");
            json.put("op", op);
            String str = LocalUtil.LoadString(request, "res.flow.Flow", "upcomingProcess");
            json.put("msg", str);
            return json;
        }

        String result = wfm.getFieldValue("cwsWorkflowResult");
        myActionDb.setResult(result);
        /*
        // 如果FinishAction在处理时抛出了异常，则不能置状态为checked，否则回到待办记录列表后，会找不到此记录
        if(op!= null && !op.trim().equals("saveformvalue") && !op.trim().equals("saveformvalueBeforeXorCondSelect")){
             myActionDb.setChecked(true);
        }
        */

        myActionDb.save();
        // lzm添加  审阅 判断
        if ("finish".equals(op)) {
            int kind = wa.getKind();
            if (kind == WorkflowActionDb.KIND_READ) {
                op = "read";
            }
        }
        // 退回
        if ("return".equals(op)) {
            re = wfm.ReturnAction(request, wf, wa, myActionId);
            if (re) {
                json.put("res", "0");
                json.put("op", op);
                json.put("msg", "操作成功！");
            } else {
                json.put("res", "-1");
                json.put("msg", "操作失败！");
                json.put("op", op);
            }
            return json;
        } else if ("del".equals(op)) {
            re = wfm.del(request, flowId);
            if (re) {
                json.put("res", "0");
                json.put("op", "del");
                json.put("msg", "操作成功!");
            } else {
                json.put("res", "-1");
                json.put("msg", "操作失败!");
                json.put("op", "del");
            }
            return json;
        } else if ("finish".equals(op)) {
            boolean flagXorRadiate = wa.isXorRadiate();
            Vector<WorkflowLinkDb> vMatched = null;
            StringBuffer condBuf = new StringBuffer();
            // 手机端匹配条件分支
            if (flagXorRadiate) {
                wfm.saveFormValue(request, wf, wa);
                request.setAttribute("myActionId", myActionId);
                vMatched = WorkflowRouter.matchNextBranch(wa, myname, condBuf, myActionId);
            }
            boolean isCondSatisfied = vMatched != null && vMatched.size() > 0;
            String conds = condBuf.toString();
            WorkflowRuler wr = new WorkflowRuler();
            WorkflowRouter workflowRouter = new WorkflowRouter();
            boolean hasCond = !"".equals(conds); // 是否含有条件
            boolean isAfterSaveformvalueBeforeXorCondSelect = wfm.getFieldValue("isAfterSaveformvalueBeforeXorCondSelect").equals("true");
            if (hasCond && !isAfterSaveformvalueBeforeXorCondSelect) {
                com.alibaba.fastjson.JSONArray users = new com.alibaba.fastjson.JSONArray();
                Vector<WorkflowActionDb> vto = wa.getLinkToActions();
                Iterator<WorkflowActionDb> toir = vto.iterator();
                WorkflowLinkDb wld = new WorkflowLinkDb();
                Iterator<UserDb> userir = null;
                // 如果条件不满足，则让用户选择默认条件（默认条件可能有多个）
                if (!isCondSatisfied) {
                    while (toir.hasNext()) {
                        WorkflowActionDb towa = (WorkflowActionDb) toir.next();
                        wld = wld.getWorkflowLinkDbForward(wa, towa);
                        // @task:是否该改为condType为-1（不需要，因为title中存储的是条件，cond_desc中才是描述
                        // 过滤掉非默认分支，不能单纯以title是否为空判断，因为还有组合条件，存于link_prop字段中
                        if (!"".equals(wld.getTitle().trim())) {
                            continue;
                        }
                        if (!WorkflowLinkDb.COND_TYPE_NONE.equals(wld.getCondType())) {
                            continue;
                        }

                        boolean isSelectable = towa.isStrategySelectable();
                        Vector<UserDb> vuser = null;
                        try {
                            String deptOfUserWithMultiDept = wfm.getFieldValue("deptOfUserWithMultiDept");
                            vuser = workflowRouter.matchActionUser(request, towa, wa, false, deptOfUserWithMultiDept);
                        } catch (MatchUserException e) {
                            json.put("res", "-1");
                            json.put("msg", e.getMessage());
                            json.put("op", "finish");
                            return json;
                        }

                        if (vuser != null && vuser.size() > 0) {
                            userir = vuser.iterator();
                            while (userir.hasNext()) {
                                UserDb ud = userir.next();
                                com.alibaba.fastjson.JSONObject user = new com.alibaba.fastjson.JSONObject();
                                user.put("actionTitle", towa.getTitle());
                                user.put("internalname", towa.getInternalName());
                                user.put("name", "WorkflowAction_" + towa.getId());
                                user.put("value", ud.getName());
                                user.put("realName", ud.getRealName());
                                user.put("isSelectable", isSelectable);
                                users.add(user);
                            }
                        } else {
                            com.alibaba.fastjson.JSONObject user = new com.alibaba.fastjson.JSONObject();
                            user.put("actionTitle", towa.getTitle());
                            user.put("internalname", towa.getInternalName());
                            user.put("name", "WorkflowAction_" + towa.getId());
                            user.put("value", "");
                            user.put("realName", "");
                            users.add(user);
                        }

                        // 如果设了“选择用户”标志位
                        if (wr.canUserSelUser(request, towa)) {
                            com.alibaba.fastjson.JSONObject user = new com.alibaba.fastjson.JSONObject();
                            user.put("actionTitle", towa.getTitle());
                            user.put("internalname", towa.getInternalName());
                            user.put("name", "WorkflowAction_" + towa.getId());
                            user.put("value", "$userSelect");
                            user.put("realName", "选择用户");
                            users.add(user);
                        }
                    }

                    json.put("res", "3");
                    json.put("users", users);
                    return json;
                } else {
                    while (toir.hasNext()) {
                        WorkflowActionDb towa = toir.next();
                        boolean isTowaMatched = false;
                        for (WorkflowLinkDb linkMatched : vMatched) {
                            if (towa.getInternalName().equals(linkMatched.getTo())) {
                                isTowaMatched = true;
                                break;
                            }
                        }
                        if (isTowaMatched) {
                            boolean isSelectable = towa.isStrategySelectable();
                            Vector<UserDb> vuser = null;
                            try {
                                String deptOfUserWithMultiDept = wfm.getFieldValue("deptOfUserWithMultiDept");
                                vuser = workflowRouter.matchActionUser(request, towa, wa, false, deptOfUserWithMultiDept);
                            } catch (MatchUserException e) {
                                // LogUtil.getLog(getClass()).error(e);
                                json.put("res", "-2");
                                json.put("msg", e.getMessage());
                                json.put("op", "finish");
                                return json;
                            }
                            if (vuser != null && vuser.size() > 0) {
                                userir = vuser.iterator();
                                while (userir.hasNext()) {
                                    UserDb ud = userir.next();
                                    com.alibaba.fastjson.JSONObject user = new com.alibaba.fastjson.JSONObject();
                                    user.put("actionTitle", towa.getTitle());
                                    user.put("internalname", towa.getInternalName());
                                    user.put("name", "WorkflowAction_" + towa.getId());
                                    user.put("value", ud.getName());
                                    user.put("realName", ud.getRealName());
                                    user.put("isSelectable", isSelectable);
                                    users.add(user);
                                }
                            } else {
                                com.alibaba.fastjson.JSONObject user = new com.alibaba.fastjson.JSONObject();
                                user.put("actionTitle", towa.getTitle());
                                user.put("internalname", towa.getInternalName());
                                user.put("name", "WorkflowAction_" + towa.getId());
                                user.put("value", "");
                                user.put("realName", "");
                                users.add(user);
                            }

                            // 如果设了“选择用户”标志位
                            if (wr.canUserSelUser(request, towa)) {
                                com.alibaba.fastjson.JSONObject user = new com.alibaba.fastjson.JSONObject();
                                user.put("actionTitle", towa.getTitle());
                                user.put("internalname", towa.getInternalName());
                                user.put("name", "WorkflowAction_" + towa.getId());
                                user.put("value", "$userSelect");
                                user.put("realName", "选择用户");
                                users.add(user);
                            }
                        }
                    }
                    if (users.size() > 0) {
                        json.put("res", "3");
                        json.put("users", users);
                        return json;
                    }
                }
            }

            // 检查是否有表单中指定的用户，如果有，则返回匹配到的人员，还是利用流程中匹配条件分支的方式，手机端无需改动
            if (!isAfterSaveformvalueBeforeXorCondSelect) {
                boolean isFieldUser = false;
                Vector<WorkflowActionDb> vto = wa.getLinkToActions();
                for (WorkflowActionDb towa : vto) {
                    if (towa.getJobCode().startsWith(WorkflowActionDb.PRE_TYPE_FIELD_USER)) {
                        isFieldUser = true;
                        break;
                    }
                }
                if (isFieldUser) {
                    com.alibaba.fastjson.JSONArray users = new com.alibaba.fastjson.JSONArray();
                    // 保存表单
                    wfm.saveFormValue(request, wf, wa);
                    for (WorkflowActionDb towa : vto) {
                        boolean isSelectable = towa.isStrategySelectable();
                        Vector<UserDb> vuser = null;
                        try {
                            vuser = workflowRouter.matchActionUser(request, towa, wa, false, null);
                        } catch (MatchUserException e) {
                            LogUtil.getLog(getClass()).error(e);
                        }
                        if (vuser != null && vuser.size() > 0) {
                            for (UserDb ud : vuser) {
                                JSONObject user = new JSONObject();
                                user.put("actionTitle", towa.getTitle());
                                user.put("internalname", towa.getInternalName());
                                user.put("name", "WorkflowAction_" + towa.getId());
                                user.put("value", ud.getName());
                                user.put("realName", ud.getRealName());
                                user.put("isSelectable", isSelectable);
                                users.add(user);
                            }
                        } else {
                            com.alibaba.fastjson.JSONObject user = new com.alibaba.fastjson.JSONObject();
                            user.put("actionTitle", towa.getTitle());
                            user.put("internalname", towa.getInternalName());
                            user.put("name", "WorkflowAction_" + towa.getId());
                            user.put("value", "");
                            user.put("realName", "");
                            users.add(user);
                        }
                    }
                    // 如果匹配到用户，则返回
                    if (users.size() > 0) {
                        json.put("res", "3");
                        json.put("users", users);
                        return json;
                    }
                }
            }
        }
        if ("finish".equals(op) || "AutoSaveArchiveNodeCommit".equals(op)) {
            re = wfm.FinishAction(request, wf, wa, myActionId);
            if (re) {
                // 如果后继节点中有一个节点是由本人继续处理，且已处于激活状态，则继续处理这个节点
                MyActionDb mad = wa.getNextActionDoingWillBeCheckedByUserSelf(privilege.getUser(request));
                if (mad != null) {
                    json.put("res", "0");
                    json.put("op", op);
                    json.put("nextMyActionId", "" + mad.getId());
                    json.put("msg", "操作成功！请点击确定，继续处理下一节点！");
                } else {
                    json.put("res", "0");
                    json.put("op", op);
                    json.put("nextMyActionId", "");
                    json.put("msg", "操作成功！");
                }
            } else {
                json.put("res", "-1");
                json.put("msg", "操作失败！");
                json.put("op", op);
            }
            return json;
        }
        if ("read".equals(op)) {
            re = wfm.read(request, actionId, myActionId);
            if (re) {
                json.put("res", "0");
                json.put("op", "finish");
                json.put("msg", "操作成功!");
            } else {
                json.put("res", "-1");
                json.put("msg", "操作失败!");
                json.put("op", "finish");
            }
            return json;
        }

        if ("manualFinish".equals(op) || "AutoSaveArchiveNodeManualFinish".equals(op) || "manualFinishAgree".equals(op)) {
            re = wfm.saveFormValue(request, wf, wa);
            if (re) {
                re = wfm.ManualFinish(request, flowId, myActionId);
            }
            if (re) {
                json.put("res", "0");
                json.put("op", op);
                json.put("msg", "操作成功！");
            } else {
                json.put("res", "-1");
                json.put("msg", "操作失败！");
                json.put("op", op);
            }
            return json;
        }

        if ("saveformvalue".equals(op)) {
            // 2013-06-29 fgf 注意保存草稿已经不再进行有效性验证
            re = wfm.saveFormValue(request, wf, wa);
            if (re) {
                json.put("res", "5");
                json.put("op", op);
                json.put("msg", "操作成功！");
            } else {
                json.put("res", "-1");
                json.put("msg", "操作失败！");
                json.put("op", op);
            }
        } else if ("editFormValue".equals(op) || "saveformvalueBeforeXorCondSelect".equals(op)) {
            re = wfm.saveFormValue(request, wf, wa);
            if (re) {
                json.put("res", "0");
                json.put("op", op);
                json.put("msg", "操作成功！");
            } else {
                json.put("res", "-1");
                json.put("msg", "操作失败！");
                json.put("op", op);
            }
        }
        return json;
    }

    @Override
    public JSONObject finishActionFreeConditional(HttpServletRequest request, Privilege privilege) throws ErrMsgException {
        if (transactionEnabled) {
            // 使用 execute 方法执行有返回值的事务操作
            return transactionTemplate.execute(status -> {
                try {
                    return finishActionFree(request, privilege);
                } catch (Exception e) {
                    // 手动标记事务回滚
                    status.setRollbackOnly();
                    if (e instanceof HackException) {
                        logService.log(authUtil.getUserName(), IPUtil.getRemoteAddr(SpringUtil.getRequest()), LogDb.TYPE_HACK, e.getMessage());
                    }
                    LogUtil.getLog(getClass()).error("finishActionFreeConditional:" + e.getMessage());
                    // 置为异常状态
                    threadContext.setAbnormal(true);
                    throw new ErrMsgException(e.getMessage());
                }
            });
        } else {
            return finishActionFree(request, privilege);
        }
    }

    @Override
    // @Transactional(rollbackFor = {Exception.class, RuntimeException.class})
    public JSONObject finishActionFree(HttpServletRequest request, Privilege privilege) throws ErrMsgException {
        WorkflowMgr wfm = new WorkflowMgr();
        JSONObject json = new JSONObject();
        try {
            wfm.doUpload(request.getServletContext(), request);
        } catch (ErrMsgException e) {
            LogUtil.getLog(getClass()).error(e);
            DebugUtil.e(getClass(), "doUpload", e.getMessage());

            json.put("ret", "0");
            json.put("msg", e.getMessage());
            json.put("op", "");
            return json;
        }

        String op = wfm.getFieldValue("op");
        String strFlowId = wfm.getFieldValue("flowId");
        int flowId = Integer.parseInt(strFlowId);
        String strActionId = wfm.getFieldValue("actionId");
        int actionId = Integer.parseInt(strActionId);
        String strMyActionId = wfm.getFieldValue("myActionId");
        long myActionId = Long.parseLong(strMyActionId);

        WorkflowDb wf = wfm.getWorkflowDb(flowId);

        WorkflowActionDb wa = new WorkflowActionDb();
        wa = wa.getWorkflowActionDb(actionId);
        if (!wa.isLoaded()) {
            String str = LocalUtil.LoadString(request, "res.flow.Flow", "notBeingHandle");
            json.put("ret", "0");
            json.put("msg", str);
            json.put("op", op);
            return json;
        }

        MyActionDb myActionDb = new MyActionDb();
        myActionDb = myActionDb.getMyActionDb(myActionId);
        String result = wfm.getFieldValue("cwsWorkflowResult");
        myActionDb.setResult(result);
        if (op != null && !"saveformvalue".equals(op.trim())) {
            myActionDb.setChecked(true);
        }
        myActionDb.save();

        if ("return".equals(op)) {
            boolean re = wfm.ReturnAction(request, wf, wa, myActionId);
            if (re) {
                json.put("ret", "1");
                json.put("op", op);
                String str = LocalUtil.LoadString(request, "res.common", "info_op_success");
                json.put("msg", str);
                return json;
            } else {
                json.put("ret", "0");
                String str = LocalUtil.LoadString(request, "res.common", "info_op_fail");
                json.put("msg", str);
                json.put("op", op);
                return json;
            }
        } else if ("finish".equals(op)) {
            boolean re;
            try {
                re = wfm.FinishActionFree(request, wf, wa, myActionId);
            } catch (ErrMsgException e1) {
                myActionDb.setChecked(false);
                myActionDb.save();
                json.put("ret", "0");
                json.put("msg", e1.getMessage());
                json.put("op", op);
                return json;
            }
            if (re) {
                // 如果后继节点中有一个节点是由本人继续处理，且已处于激活状态，则继续处理这个节点
                MyActionDb mad = wa.getNextActionDoingWillBeCheckedByUserSelf(privilege.getUser(request));
                if (mad != null) {
                    // out.print(StrUtil.Alert_Redirect("操作成功！请点击确定，继续处理下一节点！", "flow_dispose_free.jsp?myActionId=" + mad.getId()));
                    json.put("ret", "1");
                    json.put("op", op);
                    json.put("nextMyActionId", "" + mad.getId());
                    String str = LocalUtil.LoadString(request, "res.flow.Flow", "clickOk");
                    json.put("msg", str);
                    return json;
                } else {
                    json.put("ret", "1");
                    json.put("op", op);
                    json.put("nextMyActionId", "");
                    String str = LocalUtil.LoadString(request, "res.common", "info_op_success");
                    json.put("msg", str);
                    return json;
                }
            } else {
                // out.print(StrUtil.Alert_Redirect("操作失败！", "flow_dispose_free.jsp?myActionId=" + myActionId));
                json.put("ret", "0");
                String str = LocalUtil.LoadString(request, "res.common", "info_op_fail");
                json.put("msg", str);
                json.put("op", op);
                return json;
            }
        } else if ("manualFinish".equals(op)) {
            boolean re = wfm.saveFormValue(request, wf, wa);
            if (re) {
                re = wfm.ManualFinish(request, flowId, myActionId);
                if (re) {
                    myActionDb.setResultValue(WorkflowActionDb.RESULT_VALUE_DISAGGREE);
                    myActionDb.save();
                }
            }

            if (re) {
                json.put("ret", "1");
                json.put("op", op);
                String str = LocalUtil.LoadString(request, "res.common", "info_op_success");
                json.put("msg", str);
                return json;
            } else {
                json.put("ret", "0");
                String str = LocalUtil.LoadString(request, "res.common", "info_op_fail");
                json.put("msg", str);
                json.put("op", op);
                return json;
            }
        }
        // 保存草稿
        else if ("saveformvalue".equals(op)) {
            boolean re = wfm.saveFormValue(request, wf, wa);
            // afterXorCondNodeCommit通知flow_dispose.jsp页面，已保存完毕，匹配条件后，自动重定向
            if (re) {
                json.put("ret", "1");
                json.put("op", op);
                String str = LocalUtil.LoadString(request, "res.common", "info_op_success");
                json.put("msg", str);
                return json;
            }
        }
        return json;
    }

    @Override
    public JSONObject createNestSheetRelatedConditional(HttpServletRequest request) throws ErrMsgException {
        if (transactionEnabled) {
            // 使用 execute 方法执行有返回值的事务操作
            return transactionTemplate.execute(status -> {
                try {
                    return createNestSheetRelated(request);
                } catch (Exception e) {
                    // 手动标记事务回滚
                    status.setRollbackOnly();
                    if (e instanceof HackException) {
                        logService.log(authUtil.getUserName(), IPUtil.getRemoteAddr(SpringUtil.getRequest()), LogDb.TYPE_HACK, e.getMessage());
                    }
                    LogUtil.getLog(getClass()).error("createNestSheetRelatedConditional:" + e.getMessage());
                    // 置为异常状态
                    threadContext.setAbnormal(true);
                    throw new ErrMsgException(e.getMessage());
                }
            });
        } else {
            return createNestSheetRelated(request);
        }
    }

    @Override
    // @Transactional(rollbackFor = {Exception.class, RuntimeException.class})
    public JSONObject createNestSheetRelated(HttpServletRequest request) throws ErrMsgException {
        com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
        String moduleCodeRelated = ParamUtil.get(request, "moduleCodeRelated");

        String moduleCode = ParamUtil.get(request, "moduleCode");
        ModuleSetupDb msd = new ModuleSetupDb();
        msd = msd.getModuleSetupDbOrInit(moduleCode);
        if (msd == null) {
            json.put("res", 1);
            json.put("msg", "模块不存在");
            return json;
        }
        String formCode = msd.getString("form_code");

        String relateFieldValue = String.valueOf(com.redmoon.oa.visual.FormDAO.TEMP_CWS_ID);
        int parentId = ParamUtil.getInt(request, "parentId", -1); // 父模块的ID
        if (parentId == -1) {
            ModuleRelateDb mrd = new ModuleRelateDb();
            mrd = mrd.getModuleRelateDb(formCode, moduleCodeRelated);
            if (mrd == null) {
                json.put("res", 1);
                json.put("msg", "请检查模块是否相关联");
                return json;
            }
        } else {
            com.redmoon.oa.visual.FormDAOMgr fdm = new com.redmoon.oa.visual.FormDAOMgr(formCode);
            relateFieldValue = fdm.getRelateFieldValue(parentId, moduleCodeRelated);
            if (relateFieldValue == null) {
                json.put("res", 1);
                json.put("msg", "请检查模块是否相关联");
                return json;
            }
        }

        ModuleSetupDb msdRelated = new ModuleSetupDb();
        msdRelated = msdRelated.getModuleSetupDbOrInit(moduleCodeRelated);
        String formCodeRelated = msdRelated.getString("form_code");
        FormMgr fm = new FormMgr();
        FormDb fdRelated = fm.getFormDb(formCodeRelated);

        com.redmoon.oa.pvg.Privilege privilege = new com.redmoon.oa.pvg.Privilege();
        com.redmoon.oa.Config cfg = new com.redmoon.oa.Config();
        boolean isNestSheetCheckPrivilege = cfg.getBooleanProperty("isNestSheetCheckPrivilege");
        ModulePrivDb mpd = new ModulePrivDb(moduleCodeRelated);
        if (isNestSheetCheckPrivilege && !mpd.canUserAppend(privilege.getUser(request))) {
            json.put("res", 1);
            json.put("msg", cn.js.fan.web.SkinUtil.LoadString(request, "pvg_invalid"));
            return json;
        }

        long actionId = ParamUtil.getLong(request, "actionId", -1);
        request.setAttribute("actionId", String.valueOf(actionId));

        // 用于区分嵌套表是在流程还是智能模块
        boolean isVisual = false;
        boolean re;
        com.redmoon.oa.visual.FormDAOMgr fdm = new com.redmoon.oa.visual.FormDAOMgr(fdRelated);
        try {
            re = fdm.create(request.getServletContext(), request, msdRelated);
        } catch (ErrMsgException e) {
            LogUtil.getLog(getClass()).error(e);
            json.put("res", 1);
            json.put("msg", e.getMessage());
            return json;
        }
        if (re) {
            String[] fields = msdRelated.getColAry(false, "list_field");

            int len = 0;
            if (fields != null) {
                len = fields.length;
            }
            StringBuilder tds = new StringBuilder();
            String token = "#@#";
            // int cwsId = ParamUtil.getInt(request, "cws_id", com.redmoon.oa.visual.FormDAO.TEMP_CWS_ID);
            String cwsId = fdm.getFieldValue("cws_id");
            // CWS_ID_TO_ASSIGN表示在智能模块中添加操作时，添加嵌套表格2中的记录
            if (com.redmoon.oa.visual.FormDAO.CWS_ID_TO_ASSIGN.equals(cwsId)) {
                isVisual = true;
            }

            com.redmoon.oa.visual.FormDAO fdao = fdm.getFormDAO();
            RequestUtil.setFormDAO(request, fdao);
            for (int i = 0; i < len; i++) {
                String fieldName = fields[i];
                String v = StrUtil.getNullStr(fdao.getFieldHtml(request, fieldName));
                if (i == 0) {
                    tds = new StringBuilder(v);
                } else {
                    tds.append(token).append(v);
                }
            }

            json.put("res", 0);
            json.put("msg", "操作成功！");
            json.put("isVisual", isVisual);
            json.put("token", token);
            json.put("tds", tds.toString());
            json.put("fdaoId", fdm.getVisualObjId());
            json.put("formCodeRelated", formCodeRelated);

            FormDb pForm = new FormDb();
            pForm = pForm.getFormDb(formCode);
            json.put("sums", JSONObject.parse(FormUtil.getSums(fdRelated, pForm, String.valueOf(parentId)).toString()));
        } else {
            json.put("res", 1);
            json.put("msg", "操作失败！");
        }
        return json;
    }

    @Override
    public JSONObject updateNestSheetRelatedConditional(HttpServletRequest request) throws ErrMsgException {
        if (transactionEnabled) {
            // 使用 execute 方法执行有返回值的事务操作
            return transactionTemplate.execute(status -> {
                try {
                    return updateNestSheetRelated(request);
                } catch (Exception e) {
                    // 手动标记事务回滚
                    status.setRollbackOnly();
                    if (e instanceof HackException) {
                        logService.log(authUtil.getUserName(), IPUtil.getRemoteAddr(SpringUtil.getRequest()), LogDb.TYPE_HACK, e.getMessage());
                    }
                    LogUtil.getLog(getClass()).error("updateNestSheetRelatedConditional:" + e.getMessage());
                    // 置为异常状态
                    threadContext.setAbnormal(true);
                    throw new ErrMsgException(e.getMessage());
                }
            });
        } else {
            return updateNestSheetRelated(request);
        }
    }

    @Override
    // @Transactional(rollbackFor = {Exception.class, RuntimeException.class})
    public JSONObject updateNestSheetRelated(HttpServletRequest request) throws ErrMsgException {
        com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();

        String moduleCode = ParamUtil.get(request, "moduleCode"); // 主模块编码
        if ("".equals(moduleCode)) {
            json.put("res", "1");
            json.put("msg", "编码不能为空！");
            return json;
        }

        String moduleCodeRelated = ParamUtil.get(request, "moduleCodeRelated"); // 从模块编码

        com.redmoon.oa.Config cfg = new com.redmoon.oa.Config();
        boolean isNestSheetCheckPrivilege = cfg.getBooleanProperty("isNestSheetCheckPrivilege");

        Privilege privilege = new Privilege();
        ModulePrivDb mpd = new ModulePrivDb(moduleCodeRelated);
        if (isNestSheetCheckPrivilege && !mpd.canUserManage(privilege.getUser(request))) {
            json.put("res", "1");
            json.put("msg", cn.js.fan.web.SkinUtil.LoadString(request, "pvg_invalid"));
            return json;
        }

        long actionId = ParamUtil.getLong(request, "actionId", -1);
        request.setAttribute("actionId", String.valueOf(actionId));

        ModuleSetupDb msdRelated = new ModuleSetupDb();
        msdRelated = msdRelated.getModuleSetupDb(moduleCodeRelated);
        String formCodeRelated = msdRelated.getString("form_code");
        FormMgr fm = new FormMgr();
        FormDb fd = fm.getFormDb(formCodeRelated);

        long id = ParamUtil.getLong(request, "id", -1);
        if (id == -1) {
            json.put("res", "1");
            json.put("msg", SkinUtil.LoadString(request, "err_id"));
            return json;
        }

        com.redmoon.oa.visual.FormDAOMgr fdm = new com.redmoon.oa.visual.FormDAOMgr(fd);
        com.redmoon.oa.visual.FormDAO fdao = fdm.getFormDAO(id);

        // 用于区分嵌套表是在流程还是智能模块
        boolean isVisual;

        boolean re;
        try {
            re = fdm.update(request.getServletContext(), request, msdRelated);
        } catch (ErrMsgException e) {
            json.put("res", "1");
            json.put("msg", e.getMessage());
            return json;
        }
        if (re) {
            StringBuilder tds = new StringBuilder();
            String token = "#@#";
            // if (fdao.getCwsId() == com.redmoon.oa.visual.FormDAO.TEMP_CWS_ID || fdao.getCwsId().equals("" + com.redmoon.oa.visual.FormDAO.NAME_TEMP_CWS_IDS)) {
            // 智能模块中
            if (fdao.getCwsId().equals(com.redmoon.oa.visual.FormDAO.TEMP_CWS_ID) || fdao.getCwsId().equals(com.redmoon.oa.visual.FormDAO.CWS_ID_TO_ASSIGN)) {
                String[] fields = msdRelated.getColAry(false, "list_field");

                int len = 0;
                if (fields != null) {
                    len = fields.length;
                }
                fdao = fdm.getFormDAO(id);

                for (int i = 0; i < len; i++) {
                    String fieldName = fields[i];
                    String v = StrUtil.emptyTo(fdao.getFieldHtml(request, fieldName), ""); // fdao.getFieldValue(fieldName);
                    if (i == 0) {
                        tds = new StringBuilder(v);
                    } else {
                        tds.append(token).append(v);
                    }
                }
                isVisual = true;
            } else {
                isVisual = false;
            }
            json.put("res", 0);
            json.put("msg", "操作成功！");
            json.put("isVisual", isVisual);
            json.put("formCodeRelated", formCodeRelated);
            json.put("moduleCodeRelated", moduleCodeRelated);
            json.put("token", token);
            json.put("tds", tds.toString());
        } else {
            json.put("res", 1);
            json.put("msg", "操作失败！");
        }
        return json;
    }

    @Override
    @Transactional(rollbackFor = {Exception.class, RuntimeException.class})
    public String runFinishScript(HttpServletRequest request, int flowId, int actionId) throws ErrMsgException {
        com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
        BSHShell shell = null;
        WorkflowDb wf = new WorkflowDb();
        wf = wf.getWorkflowDb(flowId);

        Leaf lf = new Leaf();
        lf = lf.getLeaf(wf.getTypeCode());
        FormDb fd = new FormDb();
        fd = fd.getFormDb(lf.getFormCode());

        FormDAO fdao = new FormDAO();
        fdao = fdao.getFormDAO(flowId, fd);

        WorkflowActionDb wa = new WorkflowActionDb();
        wa = wa.getWorkflowActionDb(actionId);

        WorkflowPredefineDb wpd = new WorkflowPredefineDb();
        wpd = wpd.getDefaultPredefineFlow(wf.getTypeCode());
        WorkflowPredefineMgr wpm = new WorkflowPredefineMgr();
        String script = wpm.getOnFinishScript(wpd.getScripts());

        if (script != null && !"".equals(script.trim())) {
            shell = wf.runFinishScript(request, wf, fdao, wa, script, true);
        }

        if (shell == null) {
            return "请检查脚本是否存在";
        } else {
            String errDesc = shell.getConsole().getLogDesc();
            // json.put("msg", StrUtil.toHtml(errDesc));
            if ("".equals(errDesc)) {
                errDesc = "操作成功";
            }
            return errDesc;
        }
    }

    @Override
    public JSONObject finishActionFreeByMobileConditional(HttpServletRequest request, Privilege privilege) throws ErrMsgException {
        if (transactionEnabled) {
            // 使用 execute 方法执行有返回值的事务操作
            return transactionTemplate.execute(status -> {
                try {
                    return finishActionFreeByMobile(request, privilege);
                } catch (Exception e) {
                    // 手动标记事务回滚
                    status.setRollbackOnly();
                    if (e instanceof HackException) {
                        logService.log(authUtil.getUserName(), IPUtil.getRemoteAddr(SpringUtil.getRequest()), LogDb.TYPE_HACK, e.getMessage());
                    }
                    LogUtil.getLog(getClass()).error("finishActionFreeByMobileConditional:" + e.getMessage());
                    // 置为异常状态
                    threadContext.setAbnormal(true);
                    throw new ErrMsgException(e.getMessage());
                }
            });
        } else {
            return finishActionFreeByMobile(request, privilege);
        }
    }

    @Override
    // @Transactional(rollbackFor = {Exception.class, RuntimeException.class})
    public JSONObject finishActionFreeByMobile(HttpServletRequest request, Privilege privilege) throws ErrMsgException {
        WorkflowMgr wfm = new WorkflowMgr();
        JSONObject json = new JSONObject();

        wfm.doUpload(request.getServletContext(), request);

        String skey = wfm.getFieldValue("skey");
        com.redmoon.oa.android.Privilege prl = new com.redmoon.oa.android.Privilege();
        prl.doLogin(request, skey);

        String op = wfm.getFieldValue("op");
        String strFlowId = wfm.getFieldValue("flowId");
        int flowId = Integer.parseInt(strFlowId);
        String strActionId = wfm.getFieldValue("actionId");
        int actionId = Integer.parseInt(strActionId);
        String strMyActionId = wfm.getFieldValue("myActionId");
        long myActionId = Long.parseLong(strMyActionId);

        WorkflowDb wf = wfm.getWorkflowDb(flowId);
        WorkflowActionDb wa = new WorkflowActionDb();
        wa = wa.getWorkflowActionDb(actionId);
        if (!wa.isLoaded()) {
            json.put("res", "-1");
            json.put("msg", "没有正在办理的节点！");
            json.put("op", op);
            return json;
        }

        MyActionDb myActionDb = new MyActionDb();
        myActionDb = myActionDb.getMyActionDb(myActionId);

        String result = wfm.getFieldValue("cwsWorkflowResult");
        myActionDb.setResult(result);
        myActionDb.save();

        if ("return".equals(op)) {
            boolean re = wfm.ReturnAction(request, wf, wa, myActionId);
            if (re) {
                json.put("res", "0");
                json.put("op", op);
                json.put("msg", "操作成功！");
            } else {
                json.put("res", "-1");
                json.put("msg", "操作失败！");
                json.put("op", op);
            }
        } else if ("finish".equals(op)) {
            boolean re = wfm.FinishActionFree(request, wf, wa, myActionId);
            if (re) {
                // 如果后继节点中有一个节点是由本人继续处理，且已处于激活状态，则继续处理这个节点
                MyActionDb mad = wa.getNextActionDoingWillBeCheckedByUserSelf(privilege.getUser(request));
                if (mad != null) {
                    json.put("res", "0");
                    json.put("op", op);
                    json.put("nextMyActionId", "" + mad.getId());
                    json.put("msg", "操作成功！请点击确定，继续处理下一节点！");
                } else {
                    json.put("res", "0");
                    json.put("op", op);
                    json.put("nextMyActionId", "");
                    json.put("msg", "操作成功！");
                }
                return json;
            } else {
                json.put("res", "-1");
                json.put("msg", "操作失败！");
                json.put("op", op);
            }
        } else if ("manualFinish".equals(op)) {
            boolean re = wfm.saveFormValue(request, wf, wa);
            re = wfm.ManualFinish(request, flowId, myActionId);
            if (re) {
                myActionDb.setResultValue(WorkflowActionDb.RESULT_VALUE_DISAGGREE);
                myActionDb.save();
            }
            if (re) {
                json.put("res", "0");
                json.put("op", op);
                json.put("msg", "操作成功！");
            } else {
                json.put("res", "-1");
                json.put("msg", "操作失败！");
                json.put("op", op);
            }
            return json;
        }

        if ("saveformvalue".equals(op) || "AutoSaveArchiveNodeCommit".equals(op)) {
            boolean re = wfm.saveFormValue(request, wf, wa);
            if (re) {
                if ("saveformvalue".equals(op)) {
                    json.put("res", "5");
                    json.put("op", op);
                    json.put("msg", "保存草稿成功！");
                    return json;
                } else if ("AutoSaveArchiveNodeCommit".equals(op)) {
                    re = wfm.autoSaveArchive(request, wf, wa);
                    if (re) {
                        json.put("res", "0");
                        json.put("op", op);
                        json.put("msg", "保存存档成功！");
                    } else {
                        json.put("res", "-1");
                        json.put("op", op);
                        json.put("msg", "保存存档失败！");
                    }
                }
            }
        }
        return json;
    }

    /**
     * 匹配用户
     *
     * @param request
     * @param myActionId
     * @param actionId
     * @return
     */
    @Override
    public com.alibaba.fastjson.JSONObject matchBranchAndUser(HttpServletRequest request, long myActionId, int actionId) {
        com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
        StringBuilder info = new StringBuilder();
        String op = ParamUtil.get(request, "op");
        json.put("op", op);

        // 列出符合条件的用户
        Privilege privilege = new Privilege();
        UserMgr um = new UserMgr();
        com.redmoon.oa.Config cfg = new com.redmoon.oa.Config();
        WorkflowDb wf = new WorkflowDb();
        WorkflowActionDb wa = new WorkflowActionDb();
        wa = wa.getWorkflowActionDb(actionId);

        WorkflowRuler wr = new WorkflowRuler();
        MyActionDb mad = new MyActionDb();
        mad = mad.getMyActionDb(myActionId);

        if (mad.getActionStatus() == WorkflowActionDb.STATE_PLUS) {
            json.put("errCode", -2);
            json.put("actionStatus", WorkflowActionDb.STATE_PLUS);
            return json;
        }

        wf = wf.getWorkflowDb(wa.getFlowId());
        WorkflowPredefineDb wfp = new WorkflowPredefineDb();
        wfp = wfp.getPredefineFlowOfFree(wf.getTypeCode());

        Vector<WorkflowActionDb> vto = wa.getLinkToActions();
        // 条件分支
        boolean flagXorRadiate = wa.isXorRadiate();
        json.put("flagXorRadiate", flagXorRadiate);

        Iterator<WorkflowActionDb> toir = vto.iterator();

        // 如果在后继节点的连接线上存在条件，则判别是否有符合条件的分支，如果有满足条件的，则自动运行，注意条件分支中应只有一个分支满足条件
        // 鉴别模型 Discriminator Choice
        Vector<WorkflowLinkDb> vMatched = null;
        StringBuffer condBuf = new StringBuffer();
        if (flagXorRadiate) {
            try {
                request.setAttribute("myActionId", myActionId);
                vMatched = WorkflowRouter.matchNextBranch(wa, privilege.getUser(request), condBuf, myActionId);
            } catch (ErrMsgException e) {
                info.append(e.getMessage());
            }
        }

        boolean isCondSatisfied = vMatched != null && vMatched.size() > 0;
        String conds = condBuf.toString();
        boolean hasCond = !"".equals(conds); // 是否含有条件
        // 当op为 saveformvalueBeforeXorCondSelect 或 matchNextBranch 时，askType=1
        int askType = ParamUtil.getInt(request, "askType", 0);
        // 为发起者选择用户而匹配条件分支
        boolean isMatchForStarterSelUser = ParamUtil.getBoolean(request, "isMatchForStarterSelUser", false);

        if (hasCond && !"matchNextBranch".equals(op) && !isMatchForStarterSelUser) {
            // 选择兼职部门
            if (!"matchAfterSelDept".equals(op) && !"reMatchUser".equals(op)) {
                // fgf 20161009使当有条件时，不显示条件的相关信息，因为在提交后还会再次提示选择用户，以免引起混淆
                json.put("errCode", -2);
                json.put("info", "使当有条件时，不显示条件的相关信息");
                json.put("hasCond", hasCond);
                return json;
            }
        }

        json.put("errCode", 0);
        json.put("hasCond", hasCond);

        String flowExpireUnit = cfg.get("flowExpireUnit");

        // 找出条件为空的分支，以选择用户
        com.alibaba.fastjson.JSONArray toActions = new com.alibaba.fastjson.JSONArray();
        json.put("toActions", toActions);

        boolean isBtnSelUserShow = false;
        // 如果没有满足条件的，则显示默认分支
        if (hasCond && !isCondSatisfied) {
            int q = 0;
            WorkflowLinkDb wld = new WorkflowLinkDb();
            int isMultiDeptCount = 0;
            while (askType == 1 && toir.hasNext()) {
                WorkflowActionDb towa = toir.next();

                boolean canSelUser = wr.canUserSelUser(request, towa);
                wld = wld.getWorkflowLinkDbForward(wa, towa);
                // 过滤掉非默认分支，不能单纯以title是否为空判断，因为还有组合条件，存于link_prop字段中
                /*if (!"".equals(wld.getTitle().trim())) {
                    continue;
                }*/
                if (!WorkflowLinkDb.COND_TYPE_NONE.equals(wld.getCondType())) {
                    continue;
                }
                q++;
                JSONObject jsonWa = new JSONObject();
                toActions.add(jsonWa);
                jsonWa.put("id", towa.getId());
                jsonWa.put("internalName", towa.getInternalName());
                jsonWa.put("title", towa.getTitle());
                jsonWa.put("jobCode", towa.getJobCode());
                jsonWa.put("isDisplay", true);

                JSONObject XorActionSelected = new JSONObject();
                XorActionSelected.put("checked", false);
                XorActionSelected.put("isDisplay", false);
                XorActionSelected.put("id", "XOR" + towa.getId());
                jsonWa.put("XorActionSelected", XorActionSelected);

                com.alibaba.fastjson.JSONArray checkers = new com.alibaba.fastjson.JSONArray();
                jsonWa.put("checkers", checkers);

                // 如果预设的是用户，而不是角色
                if (towa.getJobCode().equals(WorkflowActionDb.PRE_TYPE_USER_SELECT) || towa.getJobCode().equals(WorkflowActionDb.PRE_TYPE_USER_SELECT_IN_ADMIN_DEPT)) {
                    String uName = towa.getUserName();
                    if (!StrUtil.isEmpty(uName)) {
                        String[] nameAry = StrUtil.split(uName, ",");
                        for (String userName : nameAry) {
                            JSONObject jsonChecker = new JSONObject();
                            jsonChecker.put("userName", userName);
                            jsonChecker.put("realName", um.getUserDb(userName).getRealName());
                            jsonChecker.put("checked", true);
                            jsonChecker.put("type", "checkbox");
                            checkers.add(jsonChecker);
                        }
                    }

                    isBtnSelUserShow = true;
                    jsonWa.put("isBtnSelUser", true);
                    jsonWa.put("isBtnXor", true); // 与isBtnSelUser联用，表示是在分支节点上选人人
                } else {
                    Iterator userir = null;
                    int userCount = 0;
                    try {
                        String deptOfUserWithMultiDept;
                        if (cfg.getBooleanProperty("isDeptSwitchable")) {
                            deptOfUserWithMultiDept = Privilege.getCurDeptCode();
                        } else {
                            deptOfUserWithMultiDept = ParamUtil.get(request, "deptOfUserWithMultiDept");
                        }
                        json.put("deptOfUserWithMultiDept", deptOfUserWithMultiDept);
                        WorkflowRouter workflowRouter = new WorkflowRouter();
                        Vector<UserDb> vuser = workflowRouter.matchActionUser(request, towa, wa, false, deptOfUserWithMultiDept);
                        userir = vuser.iterator();
                        userCount = vuser.size();
                    } catch (MatchUserException e) {
                        json.put("isMatchUserException", true);
                        json.put("isMultiDept", true);
                        com.alibaba.fastjson.JSONArray deptAry = new com.alibaba.fastjson.JSONArray();
                        json.put("multiDepts", deptAry);

                        if (isMultiDeptCount == 0) {
                            DeptUserDb du = new DeptUserDb();
                            DeptMgr dm = new DeptMgr();
                            // 请选择您所在的部门
                            Vector<DeptDb> vu = du.getDeptsOfUser(mad.getUserName());
                            for (DeptDb mdd : vu) {
                                String deptName = "";
                                if (!mdd.getParentCode().equals(DeptDb.ROOTCODE) && !mdd.getCode().equals(DeptDb.ROOTCODE)) {
                                    deptName = dm.getDeptDb(mdd.getParentCode()).getName() + "<span style='font-family:宋体'>&nbsp;->&nbsp;</span>" + mdd.getName();
                                } else {
                                    deptName = mdd.getName();
                                }
                                JSONObject jsonDept = new JSONObject();
                                jsonDept.put("deptCode", mdd.getCode());
                                jsonDept.put("deptName", deptName);
                                deptAry.add(jsonDept);
                            }
                            isMultiDeptCount++;
                        }
                    } catch (ErrMsgException e) {
                        json.put("errCode", -1);
                        json.put("info", e.getMessage());
                        json.put("hasCond", hasCond);
                        return json;
                    }

                    if (userir != null) {
                        while (userir.hasNext()) {
                            UserDb ud = (UserDb) userir.next();
                            boolean checked = !flagXorRadiate && userCount == 1;
                            JSONObject jsonChecker = new JSONObject();
                            jsonChecker.put("userName", ud.getName());
                            jsonChecker.put("realName", ud.getRealName());
                            jsonChecker.put("checked", checked);
                            checkers.add(jsonChecker);
                        }
                    }

                    if (canSelUser) {
                        isBtnSelUserShow = true;
                        jsonWa.put("isBtnSelUser", true);
                        jsonWa.put("isBtnXor", flagXorRadiate);
                    }
                }
                WorkflowLinkDb wld2 = wld.getWorkflowLinkDbForward(wa, towa);
                if (wld2.getExpireHour() != 0) {
                    jsonWa.put("expireHour", wld2.getExpireHour());
                    jsonWa.put("expireUnit", "day".equals(flowExpireUnit) ? "天" : "小时");
                }
            }

            if (q == 0) {
                json.put("errCode", -3);
                if (askType == 1) {
                    info.append("条件 " + conds + " 不匹配，请注意填写是否正确，重新填写后请点击提交按钮！");
                } else {
                    info.append("当前节点为条件分支，尚未匹配，请正确填写表单，提交后选择下一节点的处理人！");
                }
            }
        } else {
            // 如果满足条件，或不存在條件
            boolean isMatchUserException = false;
            String deptOfUserWithMultiDept;
            if (cfg.getBooleanProperty("isDeptSwitchable")) {
                deptOfUserWithMultiDept = Privilege.getCurDeptCode();
            } else {
                deptOfUserWithMultiDept = ParamUtil.get(request, "deptOfUserWithMultiDept");
            }
            json.put("deptOfUserWithMultiDept", deptOfUserWithMultiDept);

            WorkflowLinkDb wld = new WorkflowLinkDb();
            while (toir.hasNext()) {
                WorkflowActionDb towa = toir.next();
                // 在出现节点所设用户的同时，能否选择用户
                boolean canSelUser = wr.canUserSelUser(request, towa);

                JSONObject jsonWa = new JSONObject();

                jsonWa.put("id", towa.getId());
                jsonWa.put("internalName", towa.getInternalName());
                jsonWa.put("title", towa.getTitle());
                jsonWa.put("jobCode", towa.getJobCode());
                jsonWa.put("isDisplay", true);

                // 如果本节点是异或发散节点
                if (flagXorRadiate) {
                    // 如果有节点被忽略，则说明有后继节点被选中，但要注意全部被忽略的情况
                    if (towa.getStatus() == WorkflowActionDb.STATE_IGNORED) {
                        jsonWa.put("color", "#99CCCC");
                    }

                    // 如果存在条件，且匹配结果不为空
                    if (hasCond) {
                        if (isCondSatisfied) {
                            // 找到分支上满足条件的节点
                            boolean isTowaMatched = false;
                            for (WorkflowLinkDb linkMatched : vMatched) {
                                if (towa.getInternalName().equals(linkMatched.getTo())) {
                                    isTowaMatched = true;
                                    break;
                                }
                            }

                            jsonWa.put("isMatched", isTowaMatched);
                            if (isTowaMatched) {
                                // 匹配到的分支线
                                JSONObject XorActionSelected = new JSONObject();
                                XorActionSelected.put("checked", true);
                                XorActionSelected.put("isDisplay", false);
                                XorActionSelected.put("id", "");
                                jsonWa.put("XorActionSelected", XorActionSelected);
                            } else {
                                // 20220706 直接注释掉，不返回该分支节点的信息，因前端未判断isDisplay为false时是否显示
                                // 条件不满足，则继续寻找，不显示待选节点
                                // jsonWa.put("isDisplay", false);
                                continue;
                            }
                        }
                    } else {
                        JSONObject XorActionSelected = new JSONObject();
                        XorActionSelected.put("checked", false);
                        XorActionSelected.put("isDisplay", false);
                        XorActionSelected.put("id", "XOR" + towa.getId());
                        jsonWa.put("XorActionSelected", XorActionSelected);
                    }
                }

                toActions.add(jsonWa);

                String inputType = "checkbox";
                // 如果是子流程，则只允许选择一个用户，因为流程发起人只能是一个
                if (towa.getKind() == WorkflowActionDb.KIND_SUB_FLOW) {
                    inputType = "radio";
                }

                // 自选用户
                if (towa.getJobCode().equals(WorkflowActionDb.PRE_TYPE_USER_SELECT) || towa.getJobCode().equals(WorkflowActionDb.PRE_TYPE_USER_SELECT_IN_ADMIN_DEPT)) {
                    // 如果下一节点被设为异步提交，当前节点为被退回状态，则不能重选用户
                    boolean isNextXorFinishAndReturned = false;
                    if (towa.isXorFinish() && wa.getStatus() == WorkflowActionDb.STATE_RETURN) {
                        isNextXorFinishAndReturned = true;
                    }

                    com.alibaba.fastjson.JSONArray checkers = new com.alibaba.fastjson.JSONArray();
                    jsonWa.put("checkers", checkers);

                    String uName = towa.getUserName();
                    if (!StringUtils.isEmpty(uName)) {
                        String[] nameAry = StrUtil.split(uName, ",");
                        for (String userName : nameAry) {
                            JSONObject jsonChecker = new JSONObject();
                            checkers.add(jsonChecker);
                            jsonChecker.put("userName", userName);
                            jsonChecker.put("realName", um.getUserDb(userName).getRealName());
                            jsonChecker.put("type", "checkbox");

                            if (flagXorRadiate) {
                                jsonChecker.put("checked", false);
                                jsonChecker.put("clickXor", true);
                            } else {
                                // 如果当前节点是异步提交，或下一节点是下达模式，或下一节点为异步提交并退回至当前节点，则其他人已选的用户不能被变动
                                if (wa.isXorFinish() || towa.isStrategyGoDown() || isNextXorFinishAndReturned) {
                                    jsonChecker.put("checked", true);
                                    // 如果是异步提交或下达模式，且当前节点为退回状态，则可以重新选择用户
                                    if ((wa.isXorFinish() || towa.isStrategyGoDown()) && wa.getStatus() == WorkflowActionDb.STATE_RETURN) {
                                        jsonChecker.put("disabled", false);
                                    } else {
                                        jsonChecker.put("disabled", true);
                                    }
                                } else {
                                    jsonChecker.put("checked", true);
                                }
                            }
                        }
                    }

                    if (!isNextXorFinishAndReturned) {
                        isBtnSelUserShow = true;
                        jsonWa.put("isBtnSelUser", true);
                        jsonWa.put("isBtnXor", flagXorRadiate);
                    }
                }/* else if ((wa.getStatus() == WorkflowActionDb.STATE_RETURN || wfp.isReactive()) && StrUtil.getNullStr(towa.getJobCode()).startsWith(WorkflowActionDb.PRE_TYPE_FIELD_USER)) {
                    // 20241218 退回后，表单中指定的人员应该使可以重新选人
                    continue;
                } */else {
                    Iterator userir = null;
                    int userCount = 0;
                    try {
                        // 当异或发散不带有条件时，如果存在兼职且已选择了所在部门时
                        WorkflowRouter workflowRouter = new WorkflowRouter();
                        Vector vuser = workflowRouter.matchActionUser(request, towa, wa, false, deptOfUserWithMultiDept);
                        userir = vuser.iterator();
                        userCount = vuser.size();
                    } catch (MatchUserException e) {
                        isMatchUserException = true;
                    } catch (ErrMsgException e) {
                        json.put("errCode", -1);
                        json.put("info", e.getMessage());
                        json.put("hasCond", hasCond);
                        return json;
                    }

                    com.alibaba.fastjson.JSONArray checkers = new com.alibaba.fastjson.JSONArray();
                    jsonWa.put("checkers", checkers);
                    LogUtil.getLog(getClass()).info("jsonWa:"+jsonWa);
                    while (userir != null && userir.hasNext()) {
                        UserDb ud = (UserDb) userir.next();
                        if (ud == null || !ud.isLoaded()) {
                            userCount--;
                            continue;
                        }

                        JSONObject jsonChecker = new JSONObject();
                        checkers.add(jsonChecker);
                        jsonChecker.put("userName", ud.getName());
                        jsonChecker.put("realName", ud.getRealName());
                        jsonChecker.put("clickXor", true);

                        // 如果含条件，且分支节点上仅一个用户满足条件，则置为checked
                        // 否则仅异或发散时，需要依靠click事件去选分支
                        if (hasCond && userCount == 1) {
                            jsonChecker.put("checked", true);
                            jsonChecker.put("disabled", true);
                        } else {
                            // 如果不是异或发散，且节点上用户唯一，则选中
                            if (!flagXorRadiate) {
                                // 如果策略为不可选，或者节点上为表单中指定的用户
                                if (!towa.isStrategySelectable() || towa.getJobCode().startsWith(WorkflowActionDb.PRE_TYPE_FIELD_USER)) {
                                    jsonChecker.put("checked", true);
                                    jsonChecker.put("disabled", true);
                                } else {
                                    if (userCount == 1 && vto.size() == 1) {
                                        // 如果只有1条分支，且分支上只有一个用户，则默认被选中
                                        jsonChecker.put("checked", true);
                                    } else {
                                        jsonChecker.put("checked", towa.isStrategySelected());
                                    }
                                    jsonChecker.put("type", inputType);
                                }
                            } else {
                                // 如果策略为不可选，或者节点上为表单中指定的用户
                                if (!towa.isStrategySelectable() || towa.getJobCode().startsWith(WorkflowActionDb.PRE_TYPE_FIELD_USER)) {
                                    jsonChecker.put("checked", true);
                                    jsonChecker.put("disabled", true);
                                    jsonChecker.put("type", inputType);
                                } else {
                                    String checked = towa.isStrategySelected() ? "checked" : "";
                                    jsonChecker.put("checked", checked);
                                    jsonChecker.put("type", inputType);
                                }
                            }
                        }
                    }

                    if (canSelUser) {
                        /*if (hasCond) {
                            JSONObject XorActionSelected = new JSONObject();
                            XorActionSelected.put("checked", false);
                            XorActionSelected.put("isDisplay", true);
                            XorActionSelected.put("id", "XOR" + towa.getId());
                            jsonWa.put("XorActionSelected", XorActionSelected);
                        }*/
                        isBtnSelUserShow = true;
                        jsonWa.put("isBtnSelUser", true);
                        jsonWa.put("isBtnXor", flagXorRadiate);
                    }
                }
                WorkflowLinkDb wld2 = wld.getWorkflowLinkDbForward(wa, towa);
                if (wld2.getExpireHour() != 0) {
                    jsonWa.put("expireHour", wld2.getExpireHour());
                    jsonWa.put("expireUnit", "day".equals(flowExpireUnit) ? "天" : "小时");
                }
            }

            json.put("isMatchUserException", isMatchUserException);
            if (isMatchUserException) {
                json.put("isMultiDept", true);
                com.alibaba.fastjson.JSONArray deptAry = new com.alibaba.fastjson.JSONArray();
                json.put("multiDepts", deptAry);

                // 请选择您所在的部门
                DeptMgr dm = new DeptMgr();
                DeptUserDb du = new DeptUserDb();
                Vector<DeptDb> vu = du.getDeptsOfUser(mad.getUserName());
                for (DeptDb mdd : vu) {
                    String deptName = "";
                    if (!mdd.getParentCode().equals(DeptDb.ROOTCODE) && !mdd.getCode().equals(DeptDb.ROOTCODE)) {
                        deptName = dm.getDeptDb(mdd.getParentCode()).getName() + "->" + mdd.getName();
                    } else {
                        deptName = mdd.getName();
                    }
                    JSONObject jsonDept = new JSONObject();
                    jsonDept.put("deptCode", mdd.getCode());
                    jsonDept.put("deptName", deptName);
                    deptAry.add(jsonDept);
                }
            }
        }

        if (vto.size() == 1) {
            // 检查是否延迟，且可以更改时间
            WorkflowActionDb nwa = vto.elementAt(0);
            if (nwa.isDelayed()) {
                json.put("isDelayed", true);
                json.put("actionTitleDelayed", nwa.getTitle());
                json.put("actionJobNameDelayed", nwa.getJobName());
                json.put("isCanPrivUserModifyDelayDate", nwa.isCanPrivUserModifyDelayDate());
                json.put("timeDelayedValue", nwa.getTimeDelayedValue());
                json.put("timeDelayedUnit", nwa.getTimeDelayedUnit());
                json.put("timeDelayedUnitDesc", WorkflowActionDb.getTimeUnitDesc(nwa.getTimeDelayedUnit()));
            }
        }

        // 是否存在选择用户按钮需显示
        json.put("isBtnSelUserShow", isBtnSelUserShow);
        json.put("info", info.toString());

        return json;
    }

    @Override
    public com.alibaba.fastjson.JSONArray getToolbarButtons(WorkflowPredefineDb wpd, Leaf lf, WorkflowDb wf, MyActionDb mad, WorkflowActionDb wa, Vector vreturn, String flag, String myname, boolean isBtnSaveShow, boolean isActionKindRead, boolean canUserSeeDesignerWhenDispose, boolean canUserSeeFlowChart) {
        com.redmoon.oa.flow.FlowConfig conf = new com.redmoon.oa.flow.FlowConfig();
        com.alibaba.fastjson.JSONArray aryButton = new com.alibaba.fastjson.JSONArray();
        boolean isReadOnly = isActionKindRead;
        String btnAgreeName = WorkflowActionDb.getActionProperty(wpd, wa.getInternalName(), "btnAgreeName");
        if (!isReadOnly) {
            if (isBtnSaveShow && conf.getIsDisplay("FLOW_BUTTON_SAVE")) {
                com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
                json.put("type", "button");
                json.put("text", conf.getBtnName("FLOW_BUTTON_SAVE").startsWith("#") ? i18nUtil.get("saveDraft") : conf.getBtnName("FLOW_BUTTON_SAVE"));
                json.put("title", conf.getBtnTitle("FLOW_BUTTON_SAVE").startsWith("#") ? i18nUtil.get("noSubmit") : conf.getBtnTitle("FLOW_BUTTON_SAVE"));
                json.put("name", "save");
                aryButton.add(json);
            }
            if (mad.getCheckStatus() != MyActionDb.CHECK_STATUS_SUSPEND) {
                boolean isShow = false;
                // 取得节点上设置的同意按钮名称
                String textName = "";
                if (wf.isStarted() && wa.getIsStart() != 1) {
                    if (conf.getIsDisplay("FLOW_BUTTON_AGREE")) {
                        isShow = true;
                        if (btnAgreeName != null && !"".equals(btnAgreeName)) {
                            textName = btnAgreeName;
                        } else {
                            textName = conf.getBtnName("FLOW_BUTTON_AGREE").startsWith("#") ? i18nUtil.get("agree") : conf.getBtnName("FLOW_BUTTON_AGREE");
                        }
                    }
                } else {
                    if (conf.getIsDisplay("FLOW_BUTTON_COMMIT")) {
                        isShow = true;
                        if (btnAgreeName != null && !"".equals(btnAgreeName)) {
                            textName = btnAgreeName;
                        } else {
                            textName = conf.getBtnName("FLOW_BUTTON_COMMIT").startsWith("#") ? i18nUtil.get("submit") : conf.getBtnName("FLOW_BUTTON_COMMIT");
                        }
                    }
                }
                if (isShow) {
                    com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
                    json.put("type", "button");
                    json.put("text", textName);
                    json.put("title", conf.getBtnTitle("FLOW_BUTTON_AGREE").startsWith("#") ? i18nUtil.get("nextNode") : conf.getBtnTitle("FLOW_BUTTON_AGREE"));
                    json.put("name", "commit");
                    aryButton.add(json);
                }
            }
        } else {
            if (conf.getIsDisplay("FLOW_BUTTON_CHECK")) {
                com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
                json.put("type", "button");
                String textName;
                if (btnAgreeName != null && !"".equals(btnAgreeName)) {
                    textName = btnAgreeName;
                } else {
                    textName = conf.getBtnName("FLOW_BUTTON_CHECK").startsWith("#") ? i18nUtil.get("review") : conf.getBtnName("FLOW_BUTTON_CHECK");
                }
                json.put("text", textName);
                json.put("title", conf.getBtnTitle("FLOW_BUTTON_CHECK").startsWith("#") ? i18nUtil.get("review") : conf.getBtnTitle("FLOW_BUTTON_CHECK"));
                json.put("name", "commit");
                aryButton.add(json);
            }
        }

        if (conf.getIsDisplay("FLOW_BUTTON_PLUS") && wpd.isPlus()) {
            if (!isReadOnly && "".equals(wa.getPlus())) {
                if (mad.getCheckStatus() != MyActionDb.CHECK_STATUS_SUSPEND) {
                    com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
                    json.put("type", "button");
                    json.put("text", conf.getBtnName("FLOW_BUTTON_PLUS").startsWith("#") ? i18nUtil.get("plus") : conf.getBtnName("FLOW_BUTTON_PLUS"));
                    json.put("title", conf.getBtnTitle("FLOW_BUTTON_PLUS").startsWith("#") ? i18nUtil.get("plus") : conf.getBtnTitle("FLOW_BUTTON_PLUS"));
                    json.put("name", "plus");
                    aryButton.add(json);
                }
            }
        }

        if (conf.getIsDisplay("FLOW_BUTTON_RETURN")) {
            if (!isReadOnly && wa.isStart != 1) {
                // 加签时不允许返回，否则可能流程可能会进行不下去 fgf 20161007
                if (mad.getCheckStatus() != MyActionDb.CHECK_STATUS_SUSPEND && mad.getActionStatus() != WorkflowActionDb.STATE_PLUS) {
                    if (vreturn.size() > 0 || wpd.getReturnStyle() == WorkflowPredefineDb.RETURN_STYLE_FREE) {
                        com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
                        json.put("type", "button");
                        json.put("text", conf.getBtnName("FLOW_BUTTON_RETURN").startsWith("#") ? i18nUtil.get("back") : conf.getBtnName("FLOW_BUTTON_RETURN"));
                        json.put("title", conf.getBtnTitle("FLOW_BUTTON_RETURN").startsWith("#") ? i18nUtil.get("backNode") : conf.getBtnTitle("FLOW_BUTTON_RETURN"));
                        json.put("name", "return");
                        aryButton.add(json);
                    }
                }
            }
        }

        // 如果该节点是异或节点，则如果其后续相邻节点中有已完成的节点，说明该节点曾被激活过，那么用户可以选择不往下继续
        if (conf.getIsDisplay("FLOW_BUTTON_NOCOMMIT")) {
            if (!isReadOnly && wa.isXorAggregate()) {
                int accessedCount = wa.linkedFromActionsAccessedCount();
                if (accessedCount >= 2) {
                    if (mad.getCheckStatus() != MyActionDb.CHECK_STATUS_SUSPEND) {
                        com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
                        json.put("type", "button");
                        json.put("text", conf.getBtnName("FLOW_BUTTON_NOCOMMIT").startsWith("#") ? i18nUtil.get("completed") : conf.getBtnName("FLOW_BUTTON_NOCOMMIT"));
                        json.put("title", conf.getBtnTitle("FLOW_BUTTON_NOCOMMIT").startsWith("#") ? i18nUtil.get("noNextNode") : conf.getBtnTitle("FLOW_BUTTON_NOCOMMIT"));
                        json.put("name", "finish");
                        aryButton.add(json);
                    }
                }
            }
        }

        if (conf.getIsDisplay("FLOW_BUTTON_PROCESS")) {
            if (wf.isStarted() && wa.getIsStart() != 1 && !lf.isDebug()) {
                com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
                json.put("type", "button");
                json.put("text", conf.getBtnName("FLOW_BUTTON_PROCESS").startsWith("#") ? i18nUtil.get("process") : conf.getBtnName("FLOW_BUTTON_PROCESS"));
                json.put("title", conf.getBtnTitle("FLOW_BUTTON_PROCESS").startsWith("#") ? i18nUtil.get("showFlow") : conf.getBtnTitle("FLOW_BUTTON_PROCESS"));
                json.put("name", "process");
                aryButton.add(json);
            }
        }

        /*if (!"".equals(wpd.getDirCode()) && conf.getIsDisplay("FLOW_BUTTON_ALTER")) {
            if (wf.isStarted() && !lf.isDebug()) {
                com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
                json.put("type", "button");
                json.put("text", conf.getBtnName("FLOW_BUTTON_ALTER").startsWith("#") ? i18nUtil.get("alter") : conf.getBtnName("FLOW_BUTTON_ALTER"));
                json.put("title", conf.getBtnTitle("FLOW_BUTTON_ALTER").startsWith("#") ? i18nUtil.get("alterTitle") : conf.getBtnTitle("FLOW_BUTTON_ALTER"));
                json.put("name", "alter");
                aryButton.add(json);
            }
        }*/

        if (conf.getIsDisplay("FLOW_BUTTON_DOC")) {
            if (!isReadOnly) {
                if (mad.getCheckStatus() != MyActionDb.CHECK_STATUS_SUSPEND) {
                    com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
                    json.put("type", "button");
                    json.put("text", conf.getBtnName("FLOW_BUTTON_DOC").startsWith("#") ? i18nUtil.get("proposedText") : conf.getBtnName("FLOW_BUTTON_DOC"));
                    json.put("title", conf.getBtnTitle("FLOW_BUTTON_DOC").startsWith("#") ? i18nUtil.get("work") : conf.getBtnTitle("FLOW_BUTTON_DOC"));
                    json.put("name", "doc");
                    aryButton.add(json);
                }
            }
        }

        if (conf.getIsDisplay("FLOW_BUTTON_DISAGREE")) {
            if (!isReadOnly && wf.isStarted() && flag.length() >= 9 && flag.substring(8, 9).equals("1")) {
                if (mad.getCheckStatus() != MyActionDb.CHECK_STATUS_SUSPEND) {
                    String text = "";
                    String btnRefuseName = WorkflowActionDb.getActionProperty(wpd, wa.getInternalName(), "btnRefuseName");
                    if (btnRefuseName != null && !"".equals(btnRefuseName)) {
                        text = btnRefuseName;
                    } else {
                        text = conf.getBtnName("FLOW_BUTTON_DISAGREE").startsWith("#") ? i18nUtil.get("refuse") : conf.getBtnName("FLOW_BUTTON_DISAGREE");
                    }
                    String title = conf.getBtnTitle("FLOW_BUTTON_DISAGREE").startsWith("#") ? i18nUtil.get("refusedAndEndFlow") : conf.getBtnTitle("FLOW_BUTTON_DISAGREE");
                    com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
                    json.put("type", "button");
                    json.put("text", text);
                    json.put("title", title);
                    json.put("name", "disagree");
                    aryButton.add(json);
                }
            }
        }

        if (conf.getIsDisplay("FLOW_BUTTON_FINISH")) {
            if (!isReadOnly /*&& wf.isStarted()*/ && flag.length() >= 12 && "1".equals(flag.substring(11, 12))) {
                if (mad.getCheckStatus() != MyActionDb.CHECK_STATUS_SUSPEND) {
                    com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
                    json.put("type", "button");
                    json.put("text", conf.getBtnName("FLOW_BUTTON_FINISH").startsWith("#") ? i18nUtil.get("finish") : conf.getBtnName("FLOW_BUTTON_FINISH"));
                    json.put("title", conf.getBtnTitle("FLOW_BUTTON_FINISH").startsWith("#") ? i18nUtil.get("finishAndAgree") : conf.getBtnTitle("FLOW_BUTTON_FINISH"));
                    json.put("name", "finishAgree");
                    aryButton.add(json);
                }
            }
        }

        Privilege pvg = new Privilege();
        if (conf.getIsDisplay("FLOW_BUTTON_ZIP") && pvg.isUserPrivValid(request, "flow.zip")) {
            com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
            json.put("type", "button");
            json.put("text", conf.getBtnName("FLOW_BUTTON_ZIP").startsWith("#") ? i18nUtil.get("downloadZip") : conf.getBtnName("FLOW_BUTTON_ZIP"));
            json.put("title", conf.getBtnTitle("FLOW_BUTTON_ZIP").startsWith("#") ? i18nUtil.get("downloadZipTitle") : conf.getBtnTitle("FLOW_BUTTON_ZIP"));
            json.put("name", "zip");
            aryButton.add(json);
        }

        if (!isReadOnly && wf.isStarted()) {
            if (mad.getCheckStatus() == MyActionDb.CHECK_STATUS_SUSPEND) {
                if (conf.getIsDisplay("FLOW_BUTTON_RESUME")) {
                    com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
                    json.put("type", "button");
                    json.put("text", conf.getBtnName("FLOW_BUTTON_RESUME").startsWith("#") ? i18nUtil.get("recovery") : conf.getBtnName("FLOW_BUTTON_RESUME"));
                    json.put("title", conf.getBtnTitle("FLOW_BUTTON_RESUME").startsWith("#") ? i18nUtil.get("recoveryProcess") : conf.getBtnTitle("FLOW_BUTTON_RESUME"));
                    json.put("name", "resume");
                    aryButton.add(json);
                }
            } else {
                if (conf.getIsDisplay("FLOW_BUTTON_SUSPEND")) {
                    com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
                    json.put("type", "button");
                    json.put("text", conf.getBtnName("FLOW_BUTTON_SUSPEND").startsWith("#") ? i18nUtil.get("hangUp") : conf.getBtnName("FLOW_BUTTON_SUSPEND"));
                    json.put("title", conf.getBtnTitle("FLOW_BUTTON_SUSPEND").startsWith("#") ? i18nUtil.get("hang") : conf.getBtnTitle("FLOW_BUTTON_SUSPEND"));
                    json.put("name", "suspend");
                    aryButton.add(json);
                }
            }
        }

        if (conf.getIsDisplay("FLOW_BUTTON_TRANSFER") && (wpd.isTransfer() || wa.isTransfer())) {
            if (!isReadOnly && wf.isStarted() && wa.getIsStart() != 1 && !lf.isDebug()) {
                if (mad.getCheckStatus() != MyActionDb.CHECK_STATUS_SUSPEND) {
                    com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
                    json.put("type", "button");
                    json.put("text", conf.getBtnName("FLOW_BUTTON_TRANSFER").startsWith("#") ? i18nUtil.get("assign") : conf.getBtnName("FLOW_BUTTON_TRANSFER"));
                    json.put("title", conf.getBtnTitle("FLOW_BUTTON_TRANSFER").startsWith("#") ? i18nUtil.get("assignUser") : conf.getBtnTitle("FLOW_BUTTON_TRANSFER"));
                    json.put("name", "transfer");
                    aryButton.add(json);
                }
            }
        }

        if (conf.getIsDisplay("FLOW_BUTTON_DIRECT")) {
            if (!isReadOnly && mad.getActionStatus() == WorkflowActionDb.STATE_RETURN && wpd.getReturnMode() == WorkflowPredefineDb.RETURN_MODE_TO_RETURNER) {
                if (mad.getCheckStatus() != MyActionDb.CHECK_STATUS_SUSPEND) {
                    com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
                    json.put("type", "button");
                    json.put("text", conf.getBtnName("FLOW_BUTTON_DIRECT").startsWith("#") ? i18nUtil.get("direct") : conf.getBtnName("FLOW_BUTTON_DIRECT"));
                    json.put("title", conf.getBtnTitle("FLOW_BUTTON_DIRECT").startsWith("#") ? i18nUtil.get("directBack") : conf.getBtnTitle("FLOW_BUTTON_DIRECT"));
                    json.put("name", "toRetuner");
                    aryButton.add(json);
                }
            }
        }

        if (conf.getIsDisplay("FLOW_BUTTON_DISCARD")) {
            if (!isReadOnly && flag.length() >= 3 && "1".equals(flag.substring(2, 3))) {
                if (mad.getCheckStatus() != MyActionDb.CHECK_STATUS_SUSPEND) {
                    com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
                    json.put("type", "button");
                    json.put("text", conf.getBtnName("FLOW_BUTTON_DISCARD").startsWith("#") ? i18nUtil.get("discard") : conf.getBtnName("FLOW_BUTTON_DISCARD"));
                    json.put("title", conf.getBtnTitle("FLOW_BUTTON_DISCARD").startsWith("#") ? i18nUtil.get("discardFlow") : conf.getBtnTitle("FLOW_BUTTON_DISCARD"));
                    json.put("name", "discard");
                    aryButton.add(json);
                }
            }
        }

        if (conf.getIsDisplay("FLOW_BUTTON_READED")) {
            if (/*!isReadOnly && */wa.isFlagReaded()) {
                if (mad.getCheckStatus() != MyActionDb.CHECK_STATUS_SUSPEND) {
                    com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
                    json.put("type", "button");
                    json.put("text", conf.getBtnName("FLOW_BUTTON_READED").startsWith("#") ? i18nUtil.get("btnReaded") : conf.getBtnName("FLOW_BUTTON_READED"));
                    json.put("title", conf.getBtnTitle("FLOW_BUTTON_READED").startsWith("#") ? i18nUtil.get("btnReaded") : conf.getBtnTitle("FLOW_BUTTON_READED"));
                    json.put("name", "readed");
                    aryButton.add(json);
                }
            }
        }

        if (conf.getIsDisplay("FLOW_BUTTON_DEL") && !isReadOnly) {
            if (WorkflowMgr.canDelFlowOnAction(request, wf, wa, mad)) {
                com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
                json.put("type", "button");
                json.put("text", conf.getBtnName("FLOW_BUTTON_DEL").startsWith("#") ? i18nUtil.get("delete") : conf.getBtnName("FLOW_BUTTON_DEL"));
                json.put("title", conf.getBtnTitle("FLOW_BUTTON_DEL").startsWith("#") ? i18nUtil.get("deleteFlow") : conf.getBtnTitle("FLOW_BUTTON_DEL"));
                json.put("name", "del");
                aryButton.add(json);
            }
        }

        if (conf.getIsDisplay("FLOW_BUTTON_ARCHIVE")) {
            if (!isReadOnly && wf.isStarted() && flag.length() >= 5) {
                if (mad.getCheckStatus() != MyActionDb.CHECK_STATUS_SUSPEND) {
                    if (wa.isArchiveManual()) {
                        com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
                        json.put("type", "button");
                        json.put("text", conf.getBtnName("FLOW_BUTTON_ARCHIVE").startsWith("#") ? i18nUtil.get("archive") : conf.getBtnName("FLOW_BUTTON_ARCHIVE"));
                        json.put("title", conf.getBtnTitle("FLOW_BUTTON_ARCHIVE").startsWith("#") ? i18nUtil.get("archiveFile") : conf.getBtnTitle("FLOW_BUTTON_ARCHIVE"));
                        json.put("name", "archive");
//                        json.put("useable", "T");
//                        json.put("handler", "saveArchive(" + wf.getId() + "," + wa.getId() + ");");
                    } else if ("3".equals(flag.substring(4, 5))) {
                        com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
                        json.put("type", "button");
                        json.put("text", conf.getBtnName("FLOW_BUTTON_ARCHIVE").startsWith("#") ? i18nUtil.get("archive") : conf.getBtnName("FLOW_BUTTON_ARCHIVE"));
                        json.put("title", i18nUtil.get("archiveForm"));
                        json.put("name", "archive");
//                        json.put("useable", "T");
//                        json.put("handler", "saveArchiveGov(" + wf.getId() + "," + wa.getId() + ");");
                        aryButton.add(json);
                    }
                }
            }
        }

        if (conf.getIsDisplay("FLOW_BUTTON_DISTRIBUTE")) {
            if (!isReadOnly && (wpd.isDistribute() || wa.isDistribute())) {
                if (mad.getCheckStatus() != MyActionDb.CHECK_STATUS_SUSPEND) {
                    String text = "", title = "";
                    if (!conf.getBtnName("FLOW_BUTTON_DISTRIBUTE").startsWith("#")) {
                        text = conf.getBtnName("FLOW_BUTTON_DISTRIBUTE");
                        title = conf.getBtnTitle("FLOW_BUTTON_DISTRIBUTE");
                    } else {
                        String kind = License.getInstance().getKind();
                        if (kind.equalsIgnoreCase(License.KIND_COM)) {
                            text = i18nUtil.get("notify");
                            title = i18nUtil.get("notify");
                        } else {
                            text = i18nUtil.get("distribute");
                            title = i18nUtil.get("fileDistribute");
                        }
                    }

                    com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
                    json.put("type", "button");
                    json.put("text", text);
                    json.put("title", title);
                    json.put("name", "distribute");
//                    json.put("useable", "T");
//                    json.put("handler", "distributeDoc(" + wf.getId() + ");");
                    aryButton.add(json);
                }
            }
        }
        if (conf.getIsDisplay("FLOW_BUTTON_CHART")) {
            if (canUserSeeDesignerWhenDispose || canUserSeeFlowChart) {
                com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
                json.put("type", "button");
                json.put("text", conf.getBtnName("FLOW_BUTTON_CHART").startsWith("#") ? i18nUtil.get("flowChart") : conf.getBtnName("FLOW_BUTTON_CHART"));
                json.put("title", conf.getBtnTitle("FLOW_BUTTON_CHART").startsWith("#") ? i18nUtil.get("showFlowChart") : conf.getBtnTitle("FLOW_BUTTON_CHART"));
                json.put("name", "chart");
//                json.put("useable", "T");
//                json.put("handler", "ShowDesigner();");
                aryButton.add(json);
            }
        }

        if (!lf.isDebug() && conf.getIsDisplay("FLOW_BUTTON_ATTENTION")) {
            WorkflowFavoriteDb wffd = new WorkflowFavoriteDb();
            if (!wffd.isExist(myname, wf.getId())) {
                com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
                json.put("type", "button");
                json.put("text", conf.getBtnName("FLOW_BUTTON_ATTENTION").startsWith("#") ? i18nUtil.get("attention") : conf.getBtnName("FLOW_BUTTON_ATTENTION"));
                json.put("title", conf.getBtnTitle("FLOW_BUTTON_ATTENTION").startsWith("#") ? i18nUtil.get("attention") : conf.getBtnTitle("FLOW_BUTTON_ATTENTION"));
                json.put("name", "attention");
//                json.put("useable", "T");
//                json.put("handler", "setAttention(true);");
                aryButton.add(json);
            } else {
                com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
                json.put("type", "button");
                json.put("text", conf.getBtnName("FLOW_BUTTON_CANCEL_ATTENTION").startsWith("#") ? i18nUtil.get("cancelAttention") : conf.getBtnName("FLOW_BUTTON_CANCEL_ATTENTION"));
                json.put("title", conf.getBtnTitle("FLOW_BUTTON_CANCEL_ATTENTION").startsWith("#") ? i18nUtil.get("cancelAttention") : conf.getBtnTitle("FLOW_BUTTON_CANCEL_ATTENTION"));
                json.put("name", "cancelAttention");
//                json.put("useable", "T");
//                json.put("handler", "setAttention(false);");
                aryButton.add(json);
            }
        }

        String tester = (String) com.redmoon.oa.pvg.Privilege.getAttribute(request, com.redmoon.oa.pvg.Privilege.SESSION_OA_FLOW_TESTER); // 流程测试员
        Privilege privilege = new Privilege();
        if (tester == null && privilege.isUserPrivValid(request, "admin")) {
            com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
            json.put("type", "button");
            json.put("text", "管理");
            json.put("title", "管理流程");
            json.put("name", "manage");
//            json.put("useable", "T");
//            json.put("handler", "manage();");
            aryButton.add(json);
        }

        if (lf.isDebug()/* && com.redmoon.oa.kernel.License.getInstance().isPlatformSrc()*/) {
            com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
            json.put("type", "button");
            json.put("text", "调试");
            json.put("title", "调试面板");
            json.put("name", "debug");
//            json.put("useable", "T");
//            json.put("handler", "");
            aryButton.add(json);
        }
        return aryButton;
    }

    @Override
    public com.alibaba.fastjson.JSONArray getToolbarBottonsFree(WorkflowPredefineDb wpd, Leaf lf, WorkflowDb wf, MyActionDb mad, WorkflowActionDb wa, Vector vreturn, String myname) {
        com.redmoon.oa.flow.FlowConfig conf = new com.redmoon.oa.flow.FlowConfig();
        com.alibaba.fastjson.JSONArray aryButton = new com.alibaba.fastjson.JSONArray();

        com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
        json.put("type", "button");
        json.put("text", conf.getBtnName("FLOW_BUTTON_SAVE").startsWith("#") ? i18nUtil.get("saveDraft") : conf.getBtnName("FLOW_BUTTON_SAVE"));
        json.put("title", conf.getBtnTitle("FLOW_BUTTON_SAVE").startsWith("#") ? i18nUtil.get("noSubmit") : conf.getBtnTitle("FLOW_BUTTON_SAVE"));
        json.put("name", "save");

        // json.put("useable", "T");
        // json.put("handler", "saveDraft();");
        aryButton.add(json);

        if (mad.getCheckStatus() != MyActionDb.CHECK_STATUS_SUSPEND) {
            json = new com.alibaba.fastjson.JSONObject();
            json.put("type", "button");
            String textName;
            if (wf.isStarted() && wa.getIsStart() != 1) {
                textName = conf.getBtnName("FLOW_BUTTON_AGREE").startsWith("#") ? i18nUtil.get("agree") : conf.getBtnName("FLOW_BUTTON_AGREE");
            } else {
                textName = conf.getBtnName("FLOW_BUTTON_COMMIT").startsWith("#") ? i18nUtil.get("submit") : conf.getBtnName("FLOW_BUTTON_COMMIT");
            }
            json.put("text", textName);
            json.put("title", textName);
            json.put("name", "commit");
            aryButton.add(json);
        }

        if (mad.getActionStatus() != WorkflowActionDb.STATE_PLUS && vreturn.size() > 0) {
            json = new com.alibaba.fastjson.JSONObject();
            json.put("type", "button");
            json.put("text", conf.getBtnName("FLOW_BUTTON_RETURN").startsWith("#") ? i18nUtil.get("back") : conf.getBtnName("FLOW_BUTTON_RETURN"));
            json.put("title", conf.getBtnTitle("FLOW_BUTTON_RETURN").startsWith("#") ? i18nUtil.get("backNode") : conf.getBtnTitle("FLOW_BUTTON_RETURN"));
            json.put("name", "return");
            // json.put("useable", "T");
            // json.put("handler", "returnFlow();");
            aryButton.add(json);
        }

        if (wf.isStarted()) {
            json = new com.alibaba.fastjson.JSONObject();
            json.put("type", "button");
            json.put("text", conf.getBtnName("FLOW_BUTTON_PROCESS").startsWith("#") ? i18nUtil.get("process") : conf.getBtnName("FLOW_BUTTON_PROCESS"));
            json.put("title", conf.getBtnTitle("FLOW_BUTTON_PROCESS").startsWith("#") ? i18nUtil.get("showFlow") : conf.getBtnTitle("FLOW_BUTTON_PROCESS"));
            json.put("name", "process");
            aryButton.add(json);
        }

        if (mad.getCheckStatus() != MyActionDb.CHECK_STATUS_SUSPEND) {
            if (conf.getIsDisplay("FLOW_BUTTON_PLUS") && "".equals(wa.getPlus())) {
                json = new com.alibaba.fastjson.JSONObject();
                json.put("type", "button");
                json.put("text", conf.getBtnName("FLOW_BUTTON_PLUS").startsWith("#") ? i18nUtil.get("plus") : conf.getBtnName("FLOW_BUTTON_PLUS"));
                json.put("title", conf.getBtnTitle("FLOW_BUTTON_PLUS").startsWith("#") ? i18nUtil.get("plus") : conf.getBtnTitle("FLOW_BUTTON_PLUS"));
                json.put("name", "plus");
                aryButton.add(json);
            }
        }

        if (conf.getIsDisplay("FLOW_BUTTON_DOC")) {
            json = new com.alibaba.fastjson.JSONObject();
            json.put("type", "button");
            json.put("text", conf.getBtnName("FLOW_BUTTON_DOC").startsWith("#") ? i18nUtil.get("proposedText") : conf.getBtnName("FLOW_BUTTON_DOC"));
            json.put("title", conf.getBtnTitle("FLOW_BUTTON_DOC").startsWith("#") ? i18nUtil.get("work") : conf.getBtnTitle("FLOW_BUTTON_DOC"));
            json.put("name", "doc");
            aryButton.add(json);
        }

        UserDb user = new UserDb();
        user = user.getUserDb(myname);
        try {
            if (wf.isStarted() && wpd.canUserDo(user, wf, "stop")) {
                json = new com.alibaba.fastjson.JSONObject();
                json.put("type", "button");
                String text = conf.getBtnName("FLOW_BUTTON_DISAGREE").startsWith("#") ? i18nUtil.get("refuse") : conf.getBtnName("FLOW_BUTTON_DISAGREE");
                String title = conf.getBtnTitle("FLOW_BUTTON_DISAGREE").startsWith("#") ? i18nUtil.get("refusedAndEndFlow") : conf.getBtnTitle("FLOW_BUTTON_DISAGREE");
                json.put("text", text);
                json.put("title", title);
                json.put("name", "disagree");
                aryButton.add(json);
            }
        } catch (ErrMsgException e) {
            LogUtil.getLog(getClass()).error(e);
        }

        if (wf.isStarted()) {
            if (mad.getCheckStatus() == MyActionDb.CHECK_STATUS_SUSPEND) {
                json = new com.alibaba.fastjson.JSONObject();
                json.put("type", "button");
                json.put("text", conf.getBtnName("FLOW_BUTTON_RESUME").startsWith("#") ? i18nUtil.get("recovery") : conf.getBtnName("FLOW_BUTTON_RESUME"));
                json.put("title", conf.getBtnTitle("FLOW_BUTTON_RESUME").startsWith("#") ? i18nUtil.get("recoveryProcess") : conf.getBtnTitle("FLOW_BUTTON_RESUME"));
                json.put("name", "resume");
                aryButton.add(json);
            } else {
                json = new com.alibaba.fastjson.JSONObject();
                json.put("type", "button");
                json.put("text", "挂起");
                json.put("title", "将流程挂起，暂不处理");
                json.put("text", conf.getBtnName("FLOW_BUTTON_SUSPEND").startsWith("#") ? i18nUtil.get("hangUp") : conf.getBtnName("FLOW_BUTTON_SUSPEND"));
                json.put("title", conf.getBtnTitle("FLOW_BUTTON_SUSPEND").startsWith("#") ? i18nUtil.get("hang") : conf.getBtnTitle("FLOW_BUTTON_SUSPEND"));
                json.put("name", "suspend");
                aryButton.add(json);
            }
        }

        if (conf.getIsDisplay("FLOW_BUTTON_TRANSFER")) {
            if (mad.getCheckStatus() != MyActionDb.CHECK_STATUS_SUSPEND) {
                if (wf.isStarted()) {
                    json = new com.alibaba.fastjson.JSONObject();
                    json.put("type", "button");
                    json.put("text", conf.getBtnName("FLOW_BUTTON_TRANSFER").startsWith("#") ? i18nUtil.get("assign") : conf.getBtnName("FLOW_BUTTON_TRANSFER"));
                    json.put("title", conf.getBtnTitle("FLOW_BUTTON_TRANSFER").startsWith("#") ? i18nUtil.get("assignUser") : conf.getBtnTitle("FLOW_BUTTON_TRANSFER"));
                    json.put("name", "transfer");
                    aryButton.add(json);
                }
            }
        }

        try {
            if (wpd.canUserDo(user, wf, "del")) {
                json = new JSONObject();
                json.put("type", "button");
                json.put("text", conf.getBtnName("FLOW_BUTTON_DEL").startsWith("#") ? i18nUtil.get("delete") : conf.getBtnName("FLOW_BUTTON_DEL"));
                json.put("title", conf.getBtnTitle("FLOW_BUTTON_DEL").startsWith("#") ? i18nUtil.get("deleteFlow") : conf.getBtnTitle("FLOW_BUTTON_DEL"));
                json.put("name", "del");
                aryButton.add(json);
            }
        } catch (ErrMsgException e) {
            LogUtil.getLog(getClass()).error(e);
        }

        try {
            if (wpd.canUserDo(user, wf, "archive")) {
                json = new JSONObject();
                json.put("type", "button");
                json.put("text", conf.getBtnName("FLOW_BUTTON_ARCHIVE").startsWith("#") ? i18nUtil.get("archive") : conf.getBtnName("FLOW_BUTTON_ARCHIVE"));
                json.put("title", i18nUtil.get("archiveForm"));
                json.put("name", "archive");
                aryButton.add(json);
            }
        } catch (ErrMsgException e) {
            LogUtil.getLog(getClass()).error(e);
        }

        if (conf.getIsDisplay("FLOW_BUTTON_ATTENTION")) {
            WorkflowFavoriteDb wffd = new WorkflowFavoriteDb();
            if (!wffd.isExist(myname, wf.getId())) {
                json = new JSONObject();
                json.put("type", "button");
                json.put("text", conf.getBtnName("FLOW_BUTTON_ATTENTION").startsWith("#") ? i18nUtil.get("attention") : conf.getBtnName("FLOW_BUTTON_ATTENTION"));
                json.put("title", conf.getBtnName("FLOW_BUTTON_ATTENTION").startsWith("#") ? i18nUtil.get("attention") : conf.getBtnName("FLOW_BUTTON_ATTENTION"));
                json.put("name", "attention");
                aryButton.add(json);
            } else {
                json = new JSONObject();
                json.put("type", "button");
                json.put("text", conf.getBtnName("FLOW_BUTTON_CANCEL_ATTENTION").startsWith("#") ? i18nUtil.get("attention") : conf.getBtnName("FLOW_BUTTON_CANCEL_ATTENTION"));
                json.put("title", conf.getBtnName("FLOW_BUTTON_CANCEL_ATTENTION").startsWith("#") ? i18nUtil.get("attention") : conf.getBtnName("FLOW_BUTTON_CANCEL_ATTENTION"));
                json.put("name", "cancelAttention");
                aryButton.add(json);
            }
        }

        if (conf.getIsDisplay("FLOW_BUTTON_CHART")) {
            com.redmoon.oa.Config cfg = com.redmoon.oa.Config.getInstance();
            boolean canUserSeeDesignerWhenDispose = cfg.getBooleanProperty("canUserSeeDesignerWhenDispose");
            boolean canUserSeeFlowChart = cfg.getBooleanProperty("canUserSeeFlowChart");
            if (canUserSeeDesignerWhenDispose || canUserSeeFlowChart) {
                json = new com.alibaba.fastjson.JSONObject();
                json.put("type", "button");
                json.put("text", conf.getBtnName("FLOW_BUTTON_CHART").startsWith("#") ? i18nUtil.get("flowChart") : conf.getBtnName("FLOW_BUTTON_CHART"));
                json.put("title", conf.getBtnTitle("FLOW_BUTTON_CHART").startsWith("#") ? i18nUtil.get("showFlowChart") : conf.getBtnTitle("FLOW_BUTTON_CHART"));
                json.put("name", "chart");
                aryButton.add(json);
            }
        }

        return aryButton;
    }

    /**
     * 列出处理过程，显示于流程处理页面
     *
     * @param flowId
     * @return
     */
    @Override
    public com.alibaba.fastjson.JSONArray listProcess(int flowId) {
        com.redmoon.oa.Config cfg = com.redmoon.oa.Config.getInstance();
        String flowExpireUnit = cfg.get("flowExpireUnit");
        boolean isListReallyDisposed = cfg.getBooleanProperty("isListReallyDisposed");
        boolean isHour = !"day".equals(flowExpireUnit);

        UserMgr um = new UserMgr();
        MyActionDb mad = new MyActionDb();
        Vector<MyActionDb> vProcess = mad.getMyActionDbOfFlow(flowId);
        DeptMgr deptMgr = new DeptMgr();
        OACalendarDb oad = new OACalendarDb();
        WorkflowActionDb wad = new WorkflowActionDb();
        com.alibaba.fastjson.JSONArray aryMyAction = new com.alibaba.fastjson.JSONArray();
        int k = 0;
        for (MyActionDb madPro : vProcess) {
            // 过滤掉状态为已处理，但实际未真正处理的用户（如只需一人处理）
            if (isListReallyDisposed && madPro.isChecked() && StrUtil.isEmpty(madPro.getChecker())) {
                continue;
            }

            String userName = madPro.getUserName();
            String userRealName = "";
            String userMobile = "", userPhone = "", userEmail = "", privUserMobile = "", privUserPhone = "", privUserEmail = "", userPhoto = "";
            int userGender = 1;
            if (userName != null) {
                UserDb user = um.getUserDb(madPro.getUserName());
                userRealName = user.getRealName();
                userMobile = user.getMobile();
                userPhone = user.getPhone();
                userEmail = user.getEmail();
                userGender = user.getGender();
                userPhoto = StrUtil.emptyTo(user.getPhoto(), "");
            }

            wad = wad.getWorkflowActionDb((int) madPro.getActionId());

            JSONObject json = new JSONObject();

            String deptCodes = madPro.getDeptCodes();
            String[] depts = StrUtil.split(deptCodes, ",");
            String dts = "", deptNames = "";
            if (depts != null) {
                for (String dept : depts) {
                    DeptDb dd = deptMgr.getDeptDb(dept);
                    if (dd != null) {
                        if ("".equals(dts)) {
                            dts = dd.getName();
                        } else {
                            dts += "," + dd.getName();
                        }
                    }
                }
            }
            json.put("depts", dts);

            boolean isExpired = false;
            Date chkDate = madPro.getCheckDate();
            if (chkDate == null) {
                chkDate = new Date();
            }
            if (DateUtil.compare(chkDate, madPro.getExpireDate()) == 1) {
                isExpired = true;
            }
            json.put("isExpired", isExpired);
            json.put("userRealName", userRealName);
            json.put("userMobile", userMobile);
            json.put("userPhone", userPhone);
            json.put("userEmail", userEmail);
            json.put("userPhoto", userPhoto);
            json.put("userGender", userGender);

            String privRealName = "";
            if (madPro.getPrivMyActionId() != -1) {
                MyActionDb mad2 = madPro.getMyActionDb(madPro.getPrivMyActionId());
                if (mad2.getUserName() != null) {
                    UserDb privUser = um.getUserDb(mad2.getUserName());
                    privRealName = privUser.getRealName();
                    privUserMobile = privUser.getMobile();
                    privUserPhone = privUser.getPhone();
                    privUserEmail = privUser.getEmail();
                }
            }
            json.put("privRealName", privRealName);
            json.put("privUserMobile", privUserMobile);
            json.put("privUserPhone", privUserPhone);
            json.put("privUserEmail", privUserEmail);
            json.put("privMyActionId", mad.getPrivMyActionId());

            json.put("myActionId", madPro.getId());
            json.put("actionId", wad.getId());
            json.put("actionTitle", wad.getTitle());

            json.put("receiveDate", DateUtil.format(madPro.getReceiveDate(), "MM-dd HH:mm"));
            json.put("checkDate", DateUtil.format(madPro.getCheckDate(), "yyyy-MM-dd HH:mm"));

            String remainDateStr = "";
            if (mad.getExpireDate() != null && DateUtil.compare(new Date(), mad.getExpireDate()) == 2) {
                int[] ary = DateUtil.dateDiffDHMS(mad.getExpireDate(), new Date());
                String strDay = i18nUtil.get("day");
                String strHour = i18nUtil.get("h_hour");
                String strMinute = i18nUtil.get("minute");
                remainDateStr = ary[0] + " " + strDay + ary[1] + " " + strHour + ary[2] + " " + strMinute;
            }
            json.put("remainDate", remainDateStr);

            String workDuration;
            if (isHour) {
                try {
                    double d = oad.getWorkHourCount(madPro.getReceiveDate(), madPro.getCheckDate());
                    workDuration = NumberUtil.round(d, 1);
                } catch (ErrMsgException e) {
                    workDuration = e.getMessage();
                }
            } else {
                workDuration = String.valueOf(oad.getWorkDayCountFromDb(madPro.getReceiveDate(), madPro.getCheckDate()));
            }
            json.put("workDuration", workDuration);
            json.put("performance", NumberUtil.round(madPro.getPerformance(), 2));

            json.put("alterTime", madPro.getAlterTime());

            String checkStatusName;
            if (madPro.getChecker().equals(UserDb.SYSTEM)) {
                checkStatusName = i18nUtil.get("systemAccessed");
            } else {
                checkStatusName = madPro.getCheckStatusName();
            }

            boolean isResultDesc = false;
            String resultDesc = "";
            // 发起节点不显示“同意”
            if (k != 0 && madPro.getCheckStatus() != 0 && madPro.getCheckStatus() != MyActionDb.CHECK_STATUS_TRANSFER && madPro.getCheckStatus() != MyActionDb.CHECK_STATUS_SUSPEND) {
                if (madPro.getResultValue() != WorkflowActionDb.RESULT_VALUE_RETURN) {
                    isResultDesc = true;
                    String desc = WorkflowActionDb.getResultValueDesc(madPro.getResultValue());
                    if (!StrUtil.isEmpty(desc)) {
                        resultDesc = "(" + desc + ")";
                    }
                }
            }
            if (isResultDesc) {
                checkStatusName += resultDesc;
            }
            if (madPro.getCheckStatus() == MyActionDb.CHECK_STATUS_TRANSFER) {
                checkStatusName += "(" + madPro.getRemark() + ")";
            }
            json.put("checkStatusName", checkStatusName);
            json.put("id", madPro.getId());
            // 留言
            // json.put("result", madPro.getResult());
            json.put("result", MyActionMgr.renderResult(request, madPro));
            aryMyAction.add(json);

            k++;
        }

        return aryMyAction;
    }

    /**
     * 取得处理过程
     *
     * @param wf
     * @param myUserName
     * @param isPaperReceived
     * @param isRecall
     * @param isFlowManager
     * @param isReactive
     * @return
     */
    @Override
    public com.alibaba.fastjson.JSONArray listProcessForShow(WorkflowDb wf, String myUserName, boolean isPaperReceived, boolean isRecall, boolean isFlowManager, boolean isReactive) {
        com.redmoon.oa.Config cfg = com.redmoon.oa.Config.getInstance();
        boolean isListReallyDisposed = cfg.getBooleanProperty("isListReallyDisposed");
        com.alibaba.fastjson.JSONArray aryMyAction = new com.alibaba.fastjson.JSONArray();
        UserMgr um = new UserMgr();
        WorkflowActionDb wa = new WorkflowActionDb();
        MyActionDb mad = new MyActionDb();
        DeptMgr deptMgr = new DeptMgr();
        OACalendarDb oad = new OACalendarDb();
        int k = 0;
        Vector<MyActionDb> v = mad.getMyActionDbOfFlow(wf.getId());
        for (MyActionDb myActionDb : v) {
            // 过滤掉状态为已处理，但实际未真正处理的用户（如只需一人处理）
            if (isListReallyDisposed && myActionDb.isChecked() && StrUtil.isEmpty(myActionDb.getChecker())) {
                continue;
            }

            mad = myActionDb;

            String userName = mad.getUserName();
            String userRealName = "";
            String userMobile = "", userPhone = "", userEmail = "", privUserMobile = "", privUserPhone = "", privUserEmail = "", userPhoto = "";
            int userGender = 1;
            if (userName != null) {
                UserDb user = um.getUserDb(mad.getUserName());
                userRealName = user.getRealName();
                userMobile = user.getMobile();
                userPhone = user.getPhone();
                userEmail = user.getEmail();
                userPhoto = StrUtil.emptyTo(user.getPhoto(), "");
                userGender = user.getGender();
            }
            wa = wa.getWorkflowActionDb((int) mad.getActionId());

            com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
            json.put("id", mad.getId());
            json.put("checkStatus", mad.getCheckStatus());

            // 所选择的兼职部门
            if (!StrUtil.isEmpty(mad.getPartDept())) {
                Department department = departmentCache.getDepartment(mad.getPartDept());
                if (department != null) {
                    json.put("depts", department.getName());
                } else {
                    json.put("depts", "部门: " + mad.getPartDept() + " 不存在");
                }
            } else {
                String deptCodes = mad.getDeptCodes();
                String[] depts = StrUtil.split(deptCodes, ",");
                if (depts != null) {
                    String dts = "";
                    for (String dept : depts) {
                        DeptDb dd = deptMgr.getDeptDb(dept);
                        if (dd != null) {
                            String deptName = dd.getName();
                            if ("".equals(dts)) {
                                dts = deptName;
                            } else {
                                dts += "," + deptName;
                            }
                        }
                    }
                    if (!"".equals(dts)) {
                        json.put("depts", dts);
                    } else {
                        json.put("depts", "");
                    }
                } else {
                    StringBuffer deptNames = new StringBuffer();
                    List<DeptUser> duList = deptUserService.listByUserName(mad.getUserName());
                    for (DeptUser du : duList) {
                        Department dept = departmentCache.getDepartment(du.getDeptCode());
                        /*String deptName;
                        if (!dept.getParentCode().equals(ConstUtil.DEPT_ROOT) && !dept.getCode().equals(ConstUtil.DEPT_ROOT)) {
                            Department parentDept = departmentService.getDepartment(dept.getParentCode());
                            deptName = parentDept.getName() + dept.getName();
                        } else {
                            deptName = dept.getName();
                        }*/
                        if (dept != null) {
                            StrUtil.concat(deptNames, ", ", dept.getName());
                        } else {
                            StrUtil.concat(deptNames, ", ", "部门: " + du.getDeptCode() + " 不存在");
                        }
                    }
                    json.put("depts", deptNames);
                }
            }

            boolean isExpired = false;
            Date chkDate = mad.getCheckDate();
            if (chkDate == null) {
                chkDate = new Date();
            }
            if (DateUtil.compare(chkDate, mad.getExpireDate()) == 1) {
                isExpired = true;
            }
            json.put("isExpired", isExpired);
            json.put("expireDate", DateUtil.format(mad.getExpireDate(), "MM-dd HH:mm"));

            json.put("realName", userRealName);
            json.put("userMobile", userMobile);
            json.put("userPhone", userPhone);
            json.put("userEmail", userEmail);
            json.put("userPhoto", userPhoto);
            json.put("userGender", userGender);

            json.put("isReaded", mad.isReaded());

            String privRealName = "";
            if (mad.getPrivMyActionId() != -1) {
                MyActionDb mad2 = mad.getMyActionDb(mad.getPrivMyActionId());
                if (mad2.getUserName() != null) {
                    UserDb privUser = um.getUserDb(mad2.getUserName());
                    privRealName = privUser.getRealName();
                    privUserMobile = privUser.getMobile();
                    privUserPhone = privUser.getPhone();
                    privUserEmail = privUser.getEmail();
                } else {
                    privRealName = "无";
                }
            } else {
                privRealName = "";
            }
            json.put("privRealName", privRealName);
            json.put("privUserMobile", privUserMobile);
            json.put("privUserPhone", privUserPhone);
            json.put("privUserEmail", privUserEmail);
            json.put("privMyActionId", mad.getPrivMyActionId());

            boolean isProxy = false;
            if (!"".equals(mad.getProxyUserName())) {
                isProxy = true;
                json.put("proxy", um.getUserDb(mad.getProxyUserName()).getRealName());
            }
            json.put("isProxy", isProxy);
            json.put("actionTitle", wa.getTitle());
            json.put("receiveDate", DateUtil.format(mad.getReceiveDate(), "yyyy-MM-dd HH:mm"));

            boolean isDateDelayed = false;
            if (wa.getDateDelayed() != null) {
                isDateDelayed = true;
            } else {
                json.put("statusName", WorkflowActionDb.getStatusName(mad.getActionStatus()));

                boolean isReason = false;
                String reas = wa.getReason();
                if (reas != null && !"".equals(reas.trim())) {
                    isReason = true;
                    json.put("reason", reas);
                }
                json.put("isReason", isReason);
            }
            json.put("isDateDelayed", isDateDelayed);

            json.put("readDate", DateUtil.format(mad.getReadDate(), "MM-dd HH:mm"));
            json.put("checkDate", DateUtil.format(mad.getCheckDate(), "yyyy-MM-dd HH:mm"));

            boolean canChangeExpireDate = isFlowManager && !mad.isChecked();
            json.put("canChangeExpireDate", canChangeExpireDate);

            String remainDateStr = "";
            if (mad.getExpireDate() != null && DateUtil.compare(new Date(), mad.getExpireDate()) == 2) {
                int[] ary = DateUtil.dateDiffDHMS(mad.getExpireDate(), new Date());
                String str_day = LocalUtil.LoadString(request, "res.flow.Flow", "day");
                String str_hour = LocalUtil.LoadString(request, "res.flow.Flow", "h_hour");
                String str_minute = LocalUtil.LoadString(request, "res.flow.Flow", "minute");
                remainDateStr = ary[0] + " " + str_day + ary[1] + " " + str_hour + ary[2] + " " + str_minute;
            }
            json.put("remainDate", remainDateStr);

            String workDuration;
            String flowExpireUnit = cfg.get("flowExpireUnit");
            boolean isHour = !"day".equals(flowExpireUnit);
            if (isHour) {
                try {
                    double d = oad.getWorkHourCount(mad.getReceiveDate(), mad.getCheckDate());
                    workDuration = NumberUtil.round(d, 1);
                } catch (ErrMsgException e) {
                    workDuration = e.getMessage();
                }
            } else {
                workDuration = String.valueOf(oad.getWorkDayCountFromDb(mad.getReceiveDate(), mad.getCheckDate()));
            }
            json.put("workDuration", workDuration);
            json.put("performance", NumberUtil.round(mad.getPerformance(), 2));

            String checker = "";
            if (mad.isChecked()) {
                if (mad.getChecker().equals(UserDb.SYSTEM)) {
                    checker = i18nUtil.get("system");
                } else {
                    if (!"".equals(mad.getChecker())) {
                        checker = um.getUserDb(mad.getChecker()).getRealName();
                    }
                }
            }
            json.put("checker", checker);

            json.put("checkStatusClass", MyActionDb.getCheckStatusClass(mad.getCheckStatus()));

            String checkStatusName;
            if (mad.getChecker().equals(UserDb.SYSTEM)) {
                checkStatusName = i18nUtil.get("systemAccessed");
            } else {
                checkStatusName = mad.getCheckStatusName();
            }

            boolean isResultValueDesc = false;
            String resultValueDesc = "";
            // 发起节点不显示“同意”
            if (k != 0 && mad.getCheckStatus() != 0 && mad.getCheckStatus() != MyActionDb.CHECK_STATUS_TRANSFER && mad.getCheckStatus() != MyActionDb.CHECK_STATUS_SUSPEND) {
                if (mad.getResultValue() != WorkflowActionDb.RESULT_VALUE_RETURN) {
                    if (mad.getSubMyActionId() == MyActionDb.SUB_MYACTION_ID_NONE) {
                        isResultValueDesc = true;
                        String desc = WorkflowActionDb.getResultValueDesc(mad.getResultValue());
                        // 有可能会在config_flow.xml中把同意置为了空
                        if (!StrUtil.isEmpty(desc)) {
                            resultValueDesc = "(" + desc + ")";
                        }
                    }
                }
            }
            if (isResultValueDesc) {
                checkStatusName += resultValueDesc;
            }
            if (mad.getCheckStatus() == MyActionDb.CHECK_STATUS_TRANSFER) {
                checkStatusName += "(" + mad.getRemark() + ")";
            }
            json.put("checkStatusName", checkStatusName);

            json.put("alterTime", DateUtil.format(mad.getAlterTime(), "MM-dd HH:mm"));
            // 留言
            // json.put("result", mad.getResult());
            json.put("result", MyActionMgr.renderResult(request, mad));

            boolean isSubMyAction = false;
            if (mad.getSubMyActionId() != MyActionDb.SUB_MYACTION_ID_NONE) {
                MyActionDb submad = new MyActionDb();
                submad = submad.getMyActionDb(mad.getSubMyActionId());
                isSubMyAction = true;
                json.put("subFlowLink", "&nbsp;<a href=\"javascript:;\" onclick=\"addTab('" + i18nUtil.get("subprocess") + "', '" + request.getContextPath() + "/flowShowPage.do?flowId=" + submad.getFlowId() + "')\"><font color='red'>" + i18nUtil.get("subprocess") + "</font></a>");
            }
            json.put("isSubMyAction", isSubMyAction);

            com.redmoon.oa.flow.FlowConfig conf = new com.redmoon.oa.flow.FlowConfig();

            boolean isRecallBtnShow = false;
            if (!isPaperReceived && isRecall && mad.canRecall(myUserName)) {
                isRecallBtnShow = true;
            }
            json.put("isRecallBtnShow", isRecallBtnShow);

            boolean canHandle = true;
            // 如果流程已被放弃，则不显示“处理”按钮
            if (wf.getStatus() == WorkflowDb.STATUS_DISCARDED) {
                canHandle = false;
            }
            json.put("canHandle", canHandle);

            boolean isHandleBtnShow = false;
            if (canHandle) {
                // if (!mad.getChecker().equals(UserDb.SYSTEM)) {
                if ((myUserName.equals(mad.getUserName()) || myUserName.equals(mad.getProxyUserName())) && !mad.isChecked() && mad.getCheckStatus() != MyActionDb.CHECK_STATUS_WAITING_TO_DO) {
                    isHandleBtnShow = true;
                }
                // }
            }
            json.put("isHandleBtnShow", isHandleBtnShow);

            LeafPriv lp = new LeafPriv(wf.getTypeCode());
            boolean canUserUrge = lp.canUserUrge(authUtil.getUserName());
            boolean isRemindBtnShow = false;
            boolean canRemind = cfg.getBooleanProperty("flowCanRemind");
            if (canRemind || canUserUrge) {
                if (!isPaperReceived && !mad.isChecked() && !myUserName.equals(mad.getUserName()) && mad.getCheckStatus() != MyActionDb.CHECK_STATUS_PASS && mad.getCheckStatus() != MyActionDb.CHECK_STATUS_WAITING_TO_DO)
                {
                    isRemindBtnShow = true;
                }
            }
            json.put("isRemindBtnShow", isRemindBtnShow);
            json.put("userName", mad.getUserName());

            json.put("ip", !StrUtil.isEmpty(mad.getIp()) && !StrUtil.isEmpty(mad.getBrowser()) ? mad.getIp() + "/" + mad.getOs() + "/" + mad.getBrowser() : "");

            aryMyAction.add(json);

            k++;
        }
        return aryMyAction;
    }

    @Override
    public com.alibaba.fastjson.JSONArray listAttachmentByField(int flowId, String fieldName) throws ErrMsgException {
        WorkflowDb wf = new WorkflowDb();
        wf = wf.getWorkflowDb(flowId);
        com.alibaba.fastjson.JSONArray aryAtt = new com.alibaba.fastjson.JSONArray();
        int doc_id = wf.getDocId();
        DocumentMgr dm = new DocumentMgr();
        Document doc = dm.getDocument(doc_id);
        Config cfg = Config.getInstance();
        java.util.Vector<IAttachment> attachments = new Vector<>();
        if (doc != null) {
            attachments = doc.getAttachments(1);
        } else {
            throw new ErrMsgException("流程文档不存在");
        }

        // 从模块中再获取一次
        Leaf lf = new Leaf();
        lf = lf.getLeaf(wf.getTypeCode());
        FormDb fd = new FormDb();
        fd = fd.getFormDb(lf.getFormCode());
        FormDAO flowDao = new FormDAO();
        flowDao = flowDao.getFormDAOByCache(flowId, fd);
        com.redmoon.oa.visual.FormDAO fdao = new com.redmoon.oa.visual.FormDAO();
        fdao = fdao.getFormDAOByCache(flowDao.getId(), fd);
        attachments.addAll(fdao.listAttByField(fieldName));

        UserMgr um = new UserMgr();
        String creatorRealName = "";
        for (IAttachment am : attachments) {
            if (am.getFieldName() != null && !"".equals(am.getFieldName())) {
                // IOS上传fieldName为upload
                if (!am.getFieldName().equals(fieldName)) {
                    continue;
                }
            } else {
                continue;
            }

            UserDb creator = um.getUserDb(am.getCreator());
            if (creator.isLoaded()) {
                creatorRealName = creator.getRealName();
            } else {
                creatorRealName = creator + "不存在";
            }

            com.alibaba.fastjson.JSONObject json = new com.alibaba.fastjson.JSONObject();
            json.put("id", am.getId());
            json.put("name", am.getName());
            json.put("flowId", flowId);
            json.put("fieldName", am.getFieldName());
            json.put("creatorRealName", creatorRealName);
            json.put("createDate", DateUtil.format(am.getCreateDate(), "yyyy-MM-dd"));
            json.put("size", NumberUtil.round((double) am.getSize() / 1024000, 2) + "M");
            boolean isToPdf = false;
            if ("true".equals(cfg.get("canConvertToPDF")) && ("doc".equals(StrUtil.getFileExt(am.getName())) || "docx".equals(StrUtil.getFileExt(am.getName())))) {
                isToPdf = true;
            }
            json.put("isToPdf", isToPdf);
            json.put("diskName", am.getDiskName());
            json.put("visualPath", am.getVisualPath());

            boolean isPreview = false;
            if (cfg.getBooleanProperty("canPdfFilePreview") || cfg.getBooleanProperty("canOfficeFilePreview")) {
                String s = Global.getRealPath() + am.getVisualPath() + "/" + am.getDiskName();
                String htmlfile = s.substring(0, s.lastIndexOf(".")) + ".html";
                java.io.File fileExist = new java.io.File(htmlfile);
                if (fileExist.exists()) {
                    isPreview = true;
                }
            }
            boolean isImg = false;
            if (StrUtil.isImage(StrUtil.getFileExt(am.getDiskName()))) {
                isImg = true;
            }
            json.put("isPreview", isPreview || isImg);
            if (isPreview) {
                json.put("previewUrl", sysUtil.getRootPath() + "/" + am.getVisualPath() + "/" + am.getDiskName().substring(0, am.getDiskName().lastIndexOf(".")) + ".html");
            } else if (isImg) {
                json.put("previewUrl", sysUtil.getRootPath() + "/showImg?path=" + am.getVisualPath() + "/" + am.getDiskName());
            } else {
                json.put("previewUrl", sysUtil.getRootPath() + "/" + am.getVisualPath() + "/" + am.getDiskName());
            }
            aryAtt.add(json);
        }
        return aryAtt;
    }

    /**
     * 取得加签的描述
     *
     * @param plusJson
     * @return
     */
    @Override
    public String getPlusDesc(JSONObject plusJson) {
        String plusDesc = "";
        int plusType = plusJson.getIntValue("type");
        int plusMode = plusJson.getIntValue("mode");
        if (plusType == WorkflowActionDb.PLUS_TYPE_BEFORE) {
            plusDesc = "前加签";
        } else if (plusType == WorkflowActionDb.PLUS_TYPE_AFTER) {
            plusDesc = "后加签";
        } else {
            plusDesc = "并签";
        }
        if (plusMode == WorkflowActionDb.PLUS_MODE_ORDER) {
            plusDesc += " 顺序审批";
        } else if (plusMode == WorkflowActionDb.PLUS_MODE_ONE) {
            plusDesc += " 只需其中一人处理";
        } else {
            plusDesc += " 全部审批";
        }

        plusDesc += "(";
        List<String> userNameList = new ArrayList<>();
        String[] plusUsers = StrUtil.split(plusJson.getString("users"), ",");
        if (plusUsers != null) {
            for (String userName : plusUsers) {
                User pUser = userCache.getUser(userName);
                if (pUser != null) {
                    userNameList.add(pUser.getRealName());
                } else {
                    userNameList.add(userName + " 不存在");
                }
            }
        }
        plusDesc += org.apache.commons.lang3.StringUtils.join(userNameList, ",");
        plusDesc += ")";
        return plusDesc;
    }

    public String getSqlDistributeToMe(String op, String title, Date fromDate, Date toDate, int page, int pageSize, String field, String order, int opType) {
        String userName = authUtil.getUserName();

        boolean isSearch = "search".equals(op);

        if (StrUtil.isEmpty(field)) {
            field = "id";
        }
        String sort = order;
        if (StrUtil.isEmpty(sort)) {
            sort = "desc";
        }

        String myUnitCode = authUtil.getUserUnitCode();
        DeptUserDb dud = new DeptUserDb();
        String[] ary = dud.getUnitsOfUser(userName);

        PaperConfig pc = PaperConfig.getInstance();
        // 取得收文角色
        String swRoles = pc.getProperty("swRoles");
        String[] swAry = StrUtil.split(swRoles, ",");
        UserDb user = new UserDb();
        user = user.getUserDb(userName);
        // 检查用户是否为收文角色
        int swLen = 0;
        if (swAry != null) {
            swLen = swAry.length;
        }
        boolean isWithUnit = false;
        // 如果没有在配置文件中限定收文角色，则允许有“收文处理”权限的用户，收取分发给单位的文件
        if (swLen == 0) {
            isWithUnit = true;
        } else {
            for (int i = 0; i < swLen; i++) {
                if (user.isUserOfRole(swAry[i])) {
                    isWithUnit = true;
                    break;
                }
            }
        }

        String sql = "";
        PaperDistributeDb pdd = new PaperDistributeDb();
        // 如果用户只存在于一个单位（没有兼职单位）
        if (ary.length == 1) {
            if (isWithUnit) {
                if (!isSearch) {
                    sql = "select t1.id from " + pdd.getTable().getName() + " t1, flow f where f.id=t1.flow and f.status<>" + WorkflowDb.STATUS_DELETED + " and (to_unit=" + StrUtil.sqlstr(myUnitCode) + " or to_unit=" + StrUtil.sqlstr(userName) + ")";
                } else {
                    sql = "select t1.id from " + pdd.getTable().getName() + " t1, flow f where f.id=t1.flow and f.status<>" + WorkflowDb.STATUS_DELETED + " and (to_unit=" + StrUtil.sqlstr(myUnitCode) + " or to_unit=" + StrUtil.sqlstr(userName) + ")";
                    if (!StrUtil.isEmpty(title)) {
                        sql += " and t1.title like " + StrUtil.sqlstr("%" + title + "%");
                    }
                }
            } else {
                if (!isSearch) {
                    sql = "select t1.id from " + pdd.getTable().getName() + " t1, flow f where f.id=t1.flow and f.status<>" + WorkflowDb.STATUS_DELETED + " and to_unit=" + StrUtil.sqlstr(userName);
                } else {
                    sql = "select t1.id from " + pdd.getTable().getName() + " t1, flow f where f.id=t1.flow and f.status<>" + WorkflowDb.STATUS_DELETED + " and to_unit=" + StrUtil.sqlstr(userName);
                    if (!StrUtil.isEmpty(title)) {
                        sql += " and t1.title like " + StrUtil.sqlstr("%" + title + "%");
                    }
                }
            }
        } else {
            if (isWithUnit) {
                String units = StrUtil.sqlstr(userName); // 加上用户自己
                for (String s : ary) {
                    if ("".equals(units)) {
                        units = StrUtil.sqlstr(s);
                    } else {
                        units += "," + StrUtil.sqlstr(s);
                    }
                }
                units = "(" + units + ")";

                if (!isSearch) {
                    sql = "select t1.id from " + pdd.getTable().getName() + " t1, flow f where f.id=t1.flow and f.status<>" + WorkflowDb.STATUS_DELETED + " and to_unit in " + units;
                } else {
                    if (!StrUtil.isEmpty(title)) {
                        sql = "select t1.id from " + pdd.getTable().getName() + " t1, flow f where f.id=t1.flow and f.status<>" + WorkflowDb.STATUS_DELETED + " and to_unit in " + units + " and title like " + StrUtil.sqlstr("%" + title + "%");
                    } else {
                        sql = "select t1.id from " + pdd.getTable().getName() + " t1, flow f where f.id=t1.flow and f.status<>" + WorkflowDb.STATUS_DELETED + " and to_unit in " + units;
                    }
                }
            } else {
                if (!isSearch) {
                    sql = "select t1.id from " + pdd.getTable().getName() + " t1, flow f where f.id=t1.flow and f.status<>" + WorkflowDb.STATUS_DELETED + " and to_unit=" + StrUtil.sqlstr(userName);
                } else {
                    if (!StrUtil.isEmpty(title)) {
                        sql = "select t1.id from " + pdd.getTable().getName() + " t1, flow f where f.id=t1.flow and f.status<>" + WorkflowDb.STATUS_DELETED + " and to_unit=" + StrUtil.sqlstr(userName) + " and title like " + StrUtil.sqlstr("%" + title + "%");
                    } else {
                        sql = "select t1.id from " + pdd.getTable().getName() + " t1, flow f where f.id=t1.flow and f.status<>" + WorkflowDb.STATUS_DELETED + " and to_unit=" + StrUtil.sqlstr(userName);
                    }
                }
            }
        }

        if (fromDate != null) {
            sql += " and dis_date>=" + SQLFilter.getDateStr(DateUtil.format(fromDate, "yyyy-MM-dd"), "yyyy-MM-dd");
        }
        if (toDate != null) {
            toDate = DateUtil.addDate(toDate, 1);
            sql += " and dis_date<=" + SQLFilter.getDateStr(DateUtil.format(toDate, "yyyy-MM-dd"), "yyyy-MM-dd");
        }

        sql += " and op_type=" + opType;

        sql += " order by t1." + field + " " + sort;

        return sql;
    }

    /**
     * 抄送给我的记录
     *
     * @param page
     * @param pageSize
     * @return
     */
    @Override
    public ListResult listDistributeToMe(String op, String title, Date fromDate, Date toDate, int page, int pageSize, String field, String order, int opType) {
        String sql = getSqlDistributeToMe(op, title, fromDate, toDate, page, pageSize, field, order, opType);
        DebugUtil.i(getClass(), "listDistributeToMe", sql);
        ListResult lr;
        PaperDistributeDb pdd = new PaperDistributeDb();
        try {
            lr = pdd.listResult(sql, page, pageSize);
        } catch (ResKeyException e) {
            LogUtil.getLog(getClass()).error(e);
            throw new ErrMsgException(e.getMessage(request));
        }
        return lr;
    }

    @Override
    public ListResult listDistributeByFlow(int flowId, String op, String title, Date fromDate, Date toDate, int page, int pageSize, String field, String order) {
        String userName = authUtil.getUserName();

        boolean isSearch = "search".equals(op);

        if (StrUtil.isEmpty(field)) {
            field = "id";
        }
        String sort = order;
        if (StrUtil.isEmpty(sort)) {
            sort = "desc";
        }

        WorkflowDb wf = new WorkflowDb();
        wf = wf.getWorkflowDb(flowId);
        boolean isStarter = wf.getUserName().equals(userName);

        boolean isFlowManager = false;
        Privilege privilege = new Privilege();
        if (privilege.isUserPrivValid(request, "admin")) {
            isFlowManager = true;
        } else {
            LeafPriv lp = new LeafPriv(wf.getTypeCode());
            if (privilege.isUserPrivValid(request, "admin.flow")) {
                if (lp.canUserExamine(privilege.getUser(request))) {
                    isFlowManager = true;
                }
            }
        }

        // 如果不是发起人，则只能看到我抄送的或者抄送给我的
        boolean isAll = isStarter || isFlowManager;

        String sql = "";
        PaperDistributeDb pdd = new PaperDistributeDb();

        if (isAll) {
            sql = "select id from " + pdd.getTable().getName() + " where flow=" + flowId;
        } else {
            if (!isSearch) {
                sql = "select id from " + pdd.getTable().getName() + " where flow=" + flowId + " and (user_name=" + StrUtil.sqlstr(userName) + " or to_unit=" + StrUtil.sqlstr(userName) + ")";
            } else {
                if (!StrUtil.isEmpty(title)) {
                    sql = "select id from " + pdd.getTable().getName() + " where flow=" + flowId + " and (user_name=" + StrUtil.sqlstr(userName) + " or  to_unit=" + StrUtil.sqlstr(userName) + ") and title like " + StrUtil.sqlstr("%" + title + "%");
                } else {
                    sql = "select id from " + pdd.getTable().getName() + " where flow=" + flowId + " and (user_name=" + StrUtil.sqlstr(userName) + " or  to_unit=" + StrUtil.sqlstr(userName) + ")";
                }
            }
        }

        if (fromDate != null) {
            sql += " and dis_date>=" + SQLFilter.getDateStr(DateUtil.format(fromDate, "yyyy-MM-dd"), "yyyy-MM-dd");
        }
        if (toDate != null) {
            toDate = DateUtil.addDate(toDate, 1);
            sql += " and dis_date<=" + SQLFilter.getDateStr(DateUtil.format(toDate, "yyyy-MM-dd"), "yyyy-MM-dd");
        }

        sql += " order by " + field + " " + sort;
        DebugUtil.i(getClass(), "listDistributeToMe", sql);
        ListResult lr;
        try {
            lr = pdd.listResult(sql, page, pageSize);
        } catch (ResKeyException e) {
            LogUtil.getLog(getClass()).error(e);
            throw new ErrMsgException(e.getMessage(request));
        }
        return lr;
    }

    /**
     * 抄送给我的记录
     *
     * @param page
     * @param pageSize
     * @return
     */
    @Override
    public ListResult listMyDistribute(String op, String title, String realName, Date fromDate, Date toDate, int page, int pageSize, String field, String order, int opType) {
        String userName = authUtil.getUserName();
        String sql = "";

        boolean isSearch = "search".equals(op);

        if (StrUtil.isEmpty(field)) {
            field = "id";
        }
        String sort = order;
        if (StrUtil.isEmpty(sort)) {
            sort = "desc";
        }

        PaperDistributeDb pdd = new PaperDistributeDb();

        if (!isSearch) {
            sql = "select id from " + pdd.getTable().getName() + " where user_name=" + StrUtil.sqlstr(userName);
        } else {
            sql = "select id from " + pdd.getTable().getName() + " where user_name=" + StrUtil.sqlstr(userName);
            if (!StrUtil.isEmpty(realName)) {
                sql = "select p.id from " + pdd.getTable().getName() + " p, users u where p.user_name=" + StrUtil.sqlstr(userName) + " u.name=p.user_name and u.realname like " + StrUtil.sqlstr("%" + realName + "%");
            }
            if (!StrUtil.isEmpty(title)) {
                sql += " and title like " + StrUtil.sqlstr("%" + title + "%");
            }
            if (fromDate != null) {
                sql += " and dis_date>=" + SQLFilter.getDateStr(DateUtil.format(fromDate, "yyyy-MM-dd"), "yyyy-MM-dd");
            }
            if (toDate != null) {
                toDate = DateUtil.addDate(toDate, 1);
                sql += " and dis_date<=" + SQLFilter.getDateStr(DateUtil.format(toDate, "yyyy-MM-dd"), "yyyy-MM-dd");
            }
        }

        sql += " and op_type=" + opType;

        sql += " order by " + field + " " + sort;
        DebugUtil.i(getClass(), "listMyDistribute", sql);
        ListResult lr;
        try {
            lr = pdd.listResult(sql, page, pageSize);
        } catch (ResKeyException e) {
            LogUtil.getLog(getClass()).error(e);
            throw new ErrMsgException(e.getMessage(request));
        }
        return lr;
    }

    @Async("threadPoolTaskExecutor")
    @Override
    public void exportExcelForSearchAsync(HttpServletResponse response, String typeCode, String fields, SecurityContext securityContext, String uid) throws IOException, ErrMsgException {
        SecurityContextHolder.setContext(securityContext);

        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();

        exportExcelForSearch(request, response, typeCode, fields, uid);
    }

    @Override
    public void exportExcelForSearch(HttpServletRequest request, HttpServletResponse response, String typeCode, String fields, String uid) throws ErrMsgException, IOException {
        com.redmoon.oa.flow.Leaf lf = new com.redmoon.oa.flow.Leaf();
        lf = lf.getLeaf(typeCode);
        if (lf == null) {
            throw new ErrMsgException("typeCode=" + typeCode + " 流程类型：" + typeCode + " 不存在！");
        }

        if (!fields.equals(lf.getExportColProps())) {
            lf.setExportColProps(fields);
            lf.update();
        }

        /*com.redmoon.oa.sso.Config cfg = new com.redmoon.oa.sso.Config();
        String query = ParamUtil.get(request, "query");
        String sql = cn.js.fan.security.ThreeDesUtil.decrypthexstr(cfg.get("key"), query);*/

        FormDb fd = new FormDb();
        fd = fd.getFormDb(lf.getFormCode());

        String[] fieldsSelected = StrUtil.split(fields, ",");
        if (fieldsSelected == null) {
            fieldsSelected = new String[0];
        }

        WorkflowDb wf = new WorkflowDb();

        String ids = ParamUtil.get(request, "ids");
        Vector<WorkflowDb> v = new Vector<>();
        int count = 0;
        String sql = "";
        if (StrUtil.isEmpty(ids)) {
            sql = wf.getSqlSearch(request);
            LogUtil.getLog(getClass()).info("sql: " + sql);
            JdbcTemplate jt = new JdbcTemplate();
            ResultIterator ri = null;
            try {
                ri = jt.executeQuery(SQLFilter.getCountSql(sql));
                if (ri.hasNext()) {
                    ResultRecord rr = ri.next();
                    count = rr.getInt(1);
                }
            } catch (SQLException e) {
                LogUtil.getLog(getClass()).error(e);
            }
        } else {
            String[] aryIds = StrUtil.split(ids, ",");
            for (String str : aryIds) {
                int id = StrUtil.toInt(str, -1);
                if (id == -1) {
                    LogUtil.getLog(getClass()).error("导出出错: ids" + ids + " 中的: " + str + "对应的流程 不存在");
                    return;
                }

                wf = wf.getWorkflowDb(id);
                // 判断是否有权限，以防止非法操作
                LeafPriv lp = new LeafPriv(wf.getTypeCode());
                if (!lp.canUserQuery(authUtil.getUserName())) {
                    LogUtil.getLog(getClass()).error("导出报警: 用户" + authUtil.getUserName() + " 选择导出权限非法：" + id + " " + wf.getTitle());
                } else {
                    v.add(wf);
                }
            }
            count = v.size();
        }

        // 当异步导出Excel时
        if (sysProperties.isExportExcelAsync() && Global.getInstance().isUseCache()) {
            FormDb fdItem = new FormDb();
            fdItem = fdItem.getFormDb(ConstUtil.FORM_EXPORT_EXCEL);
            com.redmoon.oa.visual.FormDAO daoItem = new com.redmoon.oa.visual.FormDAO(fdItem);
            daoItem.setFieldValue("sql_text", StrUtil.isEmpty(ids) ? sql : ids);
            daoItem.setFieldValue("operator", authUtil.getUserName());
            daoItem.setFieldValue("record_rows", count);
            daoItem.setFieldValue("module_code", wf.getTypeCode());
            daoItem.setFieldValue("ext", "xls");
            daoItem.setCreator(authUtil.getUserName()); // 参数为用户名（创建记录者）
            daoItem.setUnitCode(authUtil.getUserUnitCode()); // 置单位编码
            try {
                daoItem.create();
            } catch (SQLException e) {
                LogUtil.getLog(getClass()).error(e);
            }

            ExportExcelItem exportExcelItem = new ExportExcelItem();
            exportExcelItem.setUid(uid);
            exportExcelItem.setRows(count);
            exportExcelItem.setCurRow(0);
            exportExcelItem.setId(daoItem.getId());
            exportExcelCache.put(uid, exportExcelItem);
        }

        OutputStream os = null;
        // 当异步导出Excel时
        if (sysProperties.isExportExcelAsync() && Global.getInstance().isUseCache()) {
            os = new FileOutputStream(FileUpload.getTempPath() + "/" + uid + ".xls");
        } else {
            response.setContentType("application/vnd.ms-excel");
            response.setHeader("Content-disposition", "attachment; filename=" + StrUtil.GBToUnicode("查询结果导出.xls"));
            os = response.getOutputStream();
        }

        WritableWorkbook wwb = null;
        Workbook wb = null;
        try {
            // File file = new File(Global.getAppPath(request) + "flow/blank.xls");
            // File file = new File(Global.getRealPath() + "flow/blank.xls");
            InputStream inputStream = configUtil.getFile("templ/blank.xls");

            wb = Workbook.getWorkbook(inputStream);

            WorkbookSettings settings = new WorkbookSettings();
            settings.setWriteAccess(null);

            // 打开一个文件的副本，并且指定数据写回到原文件
            wwb = Workbook.createWorkbook(os, wb, settings);
            WritableSheet ws = wwb.getSheet(0);

            int j = 0;
            Label t = new Label(0, j, "ID");
            ws.addCell(t);
            t = new Label(1, j, "标题");
            ws.addCell(t);
            int k = 2;

            List<String> nestFormList = new ArrayList<>();
            for (String s : fieldsSelected) {
                if (s.contains(":")) {
                    String[] ary = s.split(":");
                    nestFormList.add(ary[0]);
                    FormDb fdNest = fd.getFormDb(ary[0]);
                    FormField ff = fdNest.getFormField(ary[1]);
                    t = new Label(k, j, ff.getTitle());
                } else {
                    FormField ff = fd.getFormField(s);
                    t = new Label(k, j, ff.getTitle());
                }
                ws.addCell(t);
                k++;
            }

            t = new Label(k, j, "发起时间");
            ws.addCell(t);
            k++;
            t = new Label(k, j, "发起人");
            ws.addCell(t);
            k++;
            t = new Label(k, j, "状态");
            ws.addCell(t);
            k++;
            t = new Label(k, j, "当前办理");
            ws.addCell(t);
            k++;

            j = 1;
            if (StrUtil.isEmpty(ids)) {
                int totalPages = 1;
                int pageSize = 5;
                for (int curPage = 1; curPage <= totalPages; curPage++) {
                    ListResult lr = wf.listResult(sql, curPage, pageSize);
                    v = lr.getResult();
                    if (v.size() == 0) {
                        LogUtil.getLog(getClass()).info("导出结果为空: " + sql);
                        // return;
                    }
                    if (totalPages == 1) {
                        totalPages = (int) Math.ceil((double) lr.getTotal() / pageSize);
                    }
                    LogUtil.getLog(getClass()).info("totalPages: " + totalPages);
                    exportWorkflowRows(v, fd, j + (curPage - 1) * pageSize, ws, nestFormList, fieldsSelected, uid);
                }
            } else {
                exportWorkflowRows(v, fd, j, ws, nestFormList, fieldsSelected, uid);
            }

            wwb.write();

            // 当异步导出Excel时
            if (sysProperties.isExportExcelAsync() && Global.getInstance().isUseCache()) {
                ExportExcelItem exportExcelItem = exportExcelCache.get(uid);
                if (exportExcelItem == null) {
                    LogUtil.getLog(getClass()).error("uid: " + uid + " is not found，it is may be expired.");
                } else {
                    exportExcelItem.setUid(uid);
                    exportExcelItem.setCurRow(exportExcelItem.getRows());
                    exportExcelCache.put(uid, exportExcelItem);

                    FormDb fdItem = new FormDb();
                    fdItem = fdItem.getFormDb(ConstUtil.FORM_EXPORT_EXCEL);
                    com.redmoon.oa.visual.FormDAO daoItem = new com.redmoon.oa.visual.FormDAO();
                    daoItem = daoItem.getFormDAOByCache(exportExcelItem.getId(), fdItem);
                    daoItem.setFieldValue("finish_time", new Date());
                    daoItem.save();
                }
            }
        } catch (Exception e) {
            LogUtil.getLog(getClass()).error(e);
        } finally {
            if (wwb != null) {
                try {
                    wwb.close();
                } catch (WriteException e) {
                    LogUtil.getLog(getClass()).error(e);
                }
            }
            if (wb != null) {
                wb.close();
            }
            if (os != null) {
                os.close();
            }
        }
    }

    public void exportWorkflowRows(Vector<WorkflowDb> v, FormDb fd, Integer j, WritableSheet ws, List<String> nestFormList, String[] fieldsSelected, String uid) {
        FormDAO fdao = new FormDAO();
        MacroCtlMgr mm = new MacroCtlMgr();
        MyActionDb mad = new MyActionDb();
        UserMgr um = new UserMgr();
        try {
            for (WorkflowDb wfd : v) {
                fdao = fdao.getFormDAOByCache(wfd.getId(), fd);
                if (fdao == null) {
                    LogUtil.getLog(getClass()).error("流程: " + wfd.getId() + " " + wfd.getTitle() + " 对应的记录不存在");
                    fdao = new FormDAO();
                    continue;
                }

                // 置SQL宏控件中需要用到的fdao
                RequestUtil.setFormDAO(request, fdao);

                Label t = new Label(0, j, String.valueOf(wfd.getId()));
                ws.addCell(t);

                t = new Label(1, j, wfd.getTitle());
                ws.addCell(t);

                // 取出嵌套表的记录
                Map<String, List<com.redmoon.oa.visual.FormDAO>> mapNest = new HashMap<>();
                for (String nestFormCode : nestFormList) {
                    mapNest.put(nestFormCode, fdao.listNest(nestFormCode));
                }

                int k = 2;
                for (String s : fieldsSelected) {
                    FormField ff;
                    String str = "";
                    if (s.contains(":")) {
                        String[] ary = s.split(":");
                        List<com.redmoon.oa.visual.FormDAO> nestList = mapNest.get(ary[0]);
                        StringBuilder sb = new StringBuilder();
                        for (com.redmoon.oa.visual.FormDAO nestDao : nestList) {
                            FormField nestFf = nestDao.getFormField(ary[1]);
                            if (nestFf.getType().equals(FormField.TYPE_MACRO)) {
                                MacroCtlUnit mu = mm.getMacroCtlUnit(nestFf.getMacroType());
                                if (mu == null) {
                                    DebugUtil.e(getClass(), "exportExcel", nestFf.getTitle() + " 嵌套表 宏控件: " + nestFf.getMacroType() + " 不存在");
                                } else if (!"macro_raty".equals(mu.getCode())) {
                                    StrUtil.concat(sb, ",", StrUtil.getAbstract(request, mu.getIFormMacroCtl().converToHtml(request, nestFf, nestFf.getValue()), 1000, ""));
                                }
                            } else {
                                StrUtil.concat(sb, ",", nestFf.convertToHtml());
                            }
                        }
                        str = sb.toString();
                    } else {
                        ff = fdao.getFormField(s);
                        if (ff.getType().equals(FormField.TYPE_MACRO)) {
                            MacroCtlUnit mu = mm.getMacroCtlUnit(ff.getMacroType());
                            if (mu == null) {
                                DebugUtil.e(getClass(), "exportExcel", ff.getTitle() + " 宏控件: " + ff.getMacroType() + " 不存在");
                            } else if (!"macro_raty".equals(mu.getCode())) {
                                str = StrUtil.getAbstract(request, mu.getIFormMacroCtl().converToHtml(request, ff, ff.getValue()), 1000, "");
                            }
                        } else {
                            str = ff.convertToHtml();
                        }
                    }

                    t = new Label(k, j, str);
                    ws.addCell(t);
                    k++;
                }

                t = new Label(k, j, DateUtil.format(wfd.getBeginDate(), "yy-MM-dd HH:mm:ss"));
                ws.addCell(t);
                k++;

                String userName = StrUtil.getNullStr(wfd.getUserName());
                String realName = "";
                if (!"".equals(userName)) {
                    realName = um.getUserDb(userName).getRealName();
                }
                t = new Label(k, j, realName);
                ws.addCell(t);
                k++;
                t = new Label(k, j, wfd.getStatusDesc());
                ws.addCell(t);
                k++;

                String val = "";
                for (MyActionDb madCur : mad.getMyActionDbDoingOfFlow(wfd.getId())) {
                    if (!"".equals(val)) {
                        val += "、";
                    }
                    val += um.getUserDb(madCur.getUserName()).getRealName();
                }
                t = new Label(k, j, val);
                ws.addCell(t);
                k++;

                ExportExcelItem exportExcelItem = exportExcelCache.get(uid);
                if (exportExcelItem == null) {
                    LogUtil.getLog(getClass()).error("uid: " + uid + " is not found，it is may be expired.");
                } else {
                    exportExcelItem.setUid(uid);
                    exportExcelItem.setCurRow(j);
                    exportExcelCache.put(uid, exportExcelItem);
                }

                j++;
            }
        } catch (WriteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public com.alibaba.fastjson.JSONObject getRow(WorkflowDb wfd, FormDAO fdao, com.alibaba.fastjson.JSONArray colProps, UserMgr um, String userRealName, MyActionDb mad, MacroCtlMgr mm, Leaf leaf, int displayMode, String action, String desKey) {
        com.alibaba.fastjson.JSONObject jo = new com.alibaba.fastjson.JSONObject();
        jo.put("id", String.valueOf(mad.getId()));

        Leaf lf = leaf.getLeaf(wfd.getTypeCode());
        if (lf == null) {
            jo.put("type", wfd.getTypeCode() + " 不存在");
        } else {
            jo.put("type", lf.getType());
        }
        RequestUtil.setFormDAO(request, fdao);
        for (Object colProp : colProps) {
            com.alibaba.fastjson.JSONObject json = (com.alibaba.fastjson.JSONObject) colProp;
            // 复选框列没有field
            String fieldName;
            if (json.containsKey("field")) {
                fieldName = json.getString("field");
            } else if (json.containsKey("name")) {
                fieldName = json.getString("name");
            } else {
                continue;
            }

            Object val = "";
            if (fieldName.startsWith("f.")) {
                String fieldNameReal = fieldName.substring(2);
                if ("id".equalsIgnoreCase(fieldNameReal)) {
                    val = String.valueOf(wfd.getId());
                } else if ("flow_level".equalsIgnoreCase(fieldNameReal)) {
                    val = String.valueOf(wfd.getLevel());
                } else if ("title".equalsIgnoreCase(fieldNameReal)) {
                    val = wfd.getTitle();
                } else if ("type_code".equalsIgnoreCase(fieldNameReal)) {
                    if (lf == null) {
                        val = "流程类型" + wfd.getTypeCode() + "不存在";
                    } else {
                        val = lf.getName(request);
                    }
                } else if ("begin_date".equals(fieldNameReal) || "mydate".equals(fieldNameReal)) { // 保留mydate条件，是为了向下兼容，以免需重置后，才能显示时间
                    val = DateUtil.format(wfd.getBeginDate(), "yy-MM-dd HH:mm");
                } else if ("userName".equals(fieldNameReal)) {
                    val = userRealName;
                } else if ("finallyApply".equals(fieldNameReal)) {
                    // 取得最后一个已办理的人员
                    MyActionDb lastMad = mad.getLastMyActionDbDoneOfFlow(wfd.getId());
                    if (lastMad != null) {
                        val = um.getUserDb(lastMad.getUserName()).getRealName();
                    }
                } else if ("currentHandler".equals(fieldNameReal)) {
                    StringBuilder currentHandlerNames = new StringBuilder();
                    List<User> userList = new ArrayList<>();
                    for (MyActionDb madCur : mad.getMyActionDbDoingOfFlow(wfd.getId())) {
                        User user = userCache.getUser(madCur.getUserName());
                        if (user != null) {
                            userList.add(user);
                        }
                    }
                    if (userList.size() > 1) {
                        Comparator<User> comp = (o1, o2) -> {
                            //相当于从大到小排序，大值返回负值，往前放
                            return (int)(o2.getOrders()-o1.getOrders());
                        };
                        userList.sort(comp);
                    }

                    for (User user : userList) {
                        if (!"".equals(val)) {
                            val += "、";
                        }
                        val += user.getRealName();
                        StrUtil.concat(currentHandlerNames, ",", user.getName());
                    }
                    jo.put("currentHandlerNames", currentHandlerNames.toString());
                } else if ("urgeCount".equals(fieldNameReal)) {
                    val = workflowUrgeService.getUrgeCount(wfd.getId());
                } else if ("currentAction".equals(fieldNameReal)) {
                    WorkflowActionDb workflowActionDb = new WorkflowActionDb();
                    int currentActionColor = -1;
                    Map<Integer, Integer> map = new HashMap<>();
                    for (MyActionDb madCur : mad.getMyActionDbDoingOfFlow(wfd.getId())) {
                        workflowActionDb = workflowActionDb.getWorkflowActionDb((int) madCur.getActionId());
                        if (map.containsKey(workflowActionDb.getId())) {
                            continue;
                        } else {
                            map.put(workflowActionDb.getId(), workflowActionDb.getId());
                            if (!"".equals(val)) {
                                val += "、";
                            }
                            val += workflowActionDb.getTitle();
                        }
                        if (currentActionColor == -1) {
                            currentActionColor = workflowActionDb.getOfficeColorIndex();
                        }
                    }
                    jo.put("currentActionColorIndex", currentActionColor);
                } else if ("status".equals(fieldNameReal)) {
                    if (displayMode != WorkflowMgr.DISPLAY_MODE_DOING) {
                        jo.put("statusRaw", wfd.getStatus());
                        val = wfd.getStatusDesc();
                    } else {
                        val = WorkflowActionDb.getStatusName(mad.getActionStatus());
                        jo.put("statusRaw", mad.getActionStatus());
                    }
                } else if ("remainTime".equals(fieldNameReal)) {
                    String remainDateStr = "";
                    if (mad.getExpireDate() != null && DateUtil.compare(new Date(), mad.getExpireDate()) == 2) {
                        int[] ary = DateUtil.dateDiffDHMS(mad.getExpireDate(), new Date());
                        String str_day = i18nUtil.get("day");
                        String str_hour = i18nUtil.get("h_hour");
                        String str_minute = i18nUtil.get("minute");
                        remainDateStr = ary[0] + " " + str_day + ary[1] + " " + str_hour + ary[2] + " " + str_minute;
                        val = remainDateStr;
                    }
                } else if ("end_date".equals(fieldNameReal)) {
                    val = DateUtil.format(wfd.getEndDate(), "yy-MM-dd HH:mm");
                }
            } else {
                FormField ff = fdao.getFormField(fieldName);
                if (ff != null) {
                    if (ff.getType().equals(FormField.TYPE_MACRO)) {
                        MacroCtlUnit mu = mm.getMacroCtlUnit(ff.getMacroType());
                        if (mu == null) {
                            DebugUtil.e(getClass(), "getRow", ff.getTitle() + " 宏控件: " + ff.getMacroType() + " 不存在");
                        } else {
                            val = mu.getIFormMacroCtl().converToHtml(request, ff, fdao.getFieldValue(fieldName));
                        }
                    } else {
                        val = FuncUtil.renderFieldValue(fdao, ff);
                    }
                }
            }
            jo.put(fieldName, val);
        }

        if (displayMode == WorkflowMgr.DISPLAY_MODE_DOING) {
            jo.put("isReaded", mad.isReaded());
        } else if (displayMode == WorkflowMgr.DISPLAY_MODE_ATTEND) {
            WorkflowPredefineDb wfpd = new WorkflowPredefineDb();
            wfpd = wfpd.getDefaultPredefineFlow(lf.getCode());
            if (wfpd.isDistribute()) {
                jo.put("isDistribute", true);
            } else {
                // 如果是我的流程，则判断用户是否有抄送权限
                WorkflowActionDb wa = new WorkflowActionDb();
                List<WorkflowActionDb> list = wa.listActionsCheckedByUser(wfd.getId(), authUtil.getUserName());
                for (WorkflowActionDb wad : list) {
                    if (wad.isDistribute()) {
                        jo.put("isDistribute", true);
                        break;
                    }
                }
            }
        }

        com.redmoon.oa.flow.FlowConfig conf = new com.redmoon.oa.flow.FlowConfig();
        boolean isBtnAttentionShow = conf.getIsDisplay("FLOW_BUTTON_ATTENTION");
        if (isBtnAttentionShow) {
            WorkflowFavoriteDb workflowFavoriteDb = new WorkflowFavoriteDb();
            jo.put("isFavoriated", workflowFavoriteDb.isExist(authUtil.getUserName(), wfd.getId()));
        }

        String visitKey = ThreeDesUtil.encrypt2hex(desKey, String.valueOf(fdao.getFlowId()));
        jo.put(visitKey, visitKey);
        return jo;
    }

    @Async("threadPoolTaskExecutor")
    @Override
    public void exportExcelAsync(HttpServletResponse response, String typeCode, int displayMode, SecurityContext securityContext, String uid) throws IOException, ErrMsgException {
        SecurityContextHolder.setContext(securityContext);

        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();

        exportExcel(request, response, typeCode, displayMode, uid);
    }

    @Override
    public void exportExcel(HttpServletRequest request, HttpServletResponse response, String typeCode, int displayMode, String uid) throws ErrMsgException, IOException {
        com.alibaba.fastjson.JSONArray colProps = null;
        Leaf colLeaf = new Leaf();
        FormDb fd = new FormDb();
        if (!"".equals(typeCode)) {
            colLeaf = colLeaf.getLeaf(typeCode);
            if (colLeaf == null) {
                throw new ErrMsgException("流程类型: " + typeCode + "不存在");
            }
            fd = fd.getFormDb(colLeaf.getFormCode());
        }

        if (colLeaf.isLoaded() && !StrUtil.isEmpty(colLeaf.getColProps())) {
            colProps = com.alibaba.fastjson.JSONArray.parseArray(colLeaf.getColProps());
        }
        if (colProps == null || colProps.size() == 0) {
            colProps = com.redmoon.oa.flow.Leaf.getDefaultColProps(request, typeCode, displayMode);
        }

        WorkflowDb wf = new WorkflowDb();
        String sql = "";
        Vector<WorkflowDb> v;

        String ids = ParamUtil.get(request, "ids");
        if ("".equals(ids)) {
            if (displayMode == WorkflowMgr.DISPLAY_MODE_DOING) {
                displayMode = WorkflowMgr.DISPLAY_MODE_DOING;
                sql = wf.getSqlDoing(request);
            } else if (displayMode == WorkflowMgr.DISPLAY_MODE_MINE) {
                displayMode = WorkflowMgr.DISPLAY_MODE_MINE;
                sql = wf.getSqlMine(request);
            } else if (displayMode == WorkflowMgr.DISPLAY_MODE_FAVORIATE) {
                displayMode = WorkflowMgr.DISPLAY_MODE_FAVORIATE;
                sql = wf.getSqlFavorite(request);
            } else {
                displayMode = WorkflowMgr.DISPLAY_MODE_ATTEND;
                sql = wf.getSqlAttend(request);
            }

            LogUtil.getLog(getClass()).info("sql: " + sql);

            if (displayMode == WorkflowMgr.DISPLAY_MODE_DOING) {
                v = new Vector<>();
                MyActionDb mad = new MyActionDb();
                Vector<MyActionDb> vMad = mad.list(sql);
                for (MyActionDb myActionDb : vMad) {
                    mad = myActionDb;
                    wf = wf.getWorkflowDb((int) mad.getFlowId());
                    v.add(wf);
                }
            } else {
                v = wf.list(sql);
            }
        } else {
            v = new Vector<>();
            String[] aryIds = StrUtil.split(ids, ",");
            MyActionDb mad = new MyActionDb();
            for (String str : aryIds) {
                int id = StrUtil.toInt(str, -1);
                if (id == -1) {
                    LogUtil.getLog(getClass()).error("导出出错: ids" + ids + " 中的: " + str + "对应的流程 不存在");
                    return;
                }
                if (displayMode == WorkflowMgr.DISPLAY_MODE_DOING) {
                    mad = mad.getMyActionDb(id);
                    wf = wf.getWorkflowDb((int) mad.getFlowId());
                } else {
                    wf = wf.getWorkflowDb(id);
                }
                if (wf.isUserAttended(authUtil.getUserName())) {
                    v.add(wf);
                } else {
                    LogUtil.getLog(getClass()).error("导出报警: 用户" + authUtil.getUserName() + " 未参与流程: " + id + " " + wf.getTitle());
                }
            }
        }

        // 当异步导出Excel时
        if (sysProperties.isExportExcelAsync() && Global.getInstance().isUseCache()) {
            FormDb fdItem = new FormDb();
            fdItem = fdItem.getFormDb(ConstUtil.FORM_EXPORT_EXCEL);
            com.redmoon.oa.visual.FormDAO daoItem = new com.redmoon.oa.visual.FormDAO(fdItem);
            daoItem.setFieldValue("sql_text", StrUtil.isEmpty(ids) ? sql : ids);
            daoItem.setFieldValue("operator", authUtil.getUserName());
            daoItem.setFieldValue("record_rows", v.size());
            daoItem.setFieldValue("module_code", wf.getTypeCode());
            daoItem.setFieldValue("ext", "xls");
            daoItem.setCreator(authUtil.getUserName()); // 参数为用户名（创建记录者）
            daoItem.setUnitCode(authUtil.getUserUnitCode()); // 置单位编码
            try {
                daoItem.create();
            } catch (SQLException e) {
                LogUtil.getLog(getClass()).error(e);
            }

            ExportExcelItem exportExcelItem = new ExportExcelItem();
            exportExcelItem.setUid(uid);
            exportExcelItem.setRows(v.size());
            exportExcelItem.setCurRow(0);
            exportExcelItem.setId(daoItem.getId());
            exportExcelCache.put(uid, exportExcelItem);
        }

        com.redmoon.oa.sso.Config ssoconfig = new com.redmoon.oa.sso.Config();
        String desKey = ssoconfig.get("key");

        OutputStream os;
        // 当异步导出Excel时
        if (sysProperties.isExportExcelAsync() && Global.getInstance().isUseCache()) {
            os = new FileOutputStream(FileUpload.getTempPath() + "/" + uid + ".xls");
        } else {
            response.setContentType("application/vnd.ms-excel");
            response.setHeader("Content-disposition", "attachment; filename=" + StrUtil.GBToUnicode("查询结果导出.xls"));
            os = response.getOutputStream();
        }

        WritableWorkbook wwb = null;
        Workbook wb = null;
        try {
            InputStream inputStream = configUtil.getFile("templ/blank.xls");

            wb = Workbook.getWorkbook(inputStream);

            WorkbookSettings settings = new WorkbookSettings();
            settings.setWriteAccess(null);

            // 打开一个文件的副本，并且指定数据写回到原文件
            wwb = Workbook.createWorkbook(os, wb, settings);
            WritableSheet ws = wwb.getSheet(0);

            Iterator<WorkflowDb> ir = v.iterator();
            UserMgr um = new UserMgr();
            FormDAO fdao = new FormDAO();

            int j = 0;
            int k = 0;

            for (Object o : colProps) {
                JSONObject json = (JSONObject) o;
                if (json.containsKey("title")) {
                    // 表单中的字段
                    Label t = new Label(k, j, json.getString("title"));
                    ws.addCell(t);
                } else if (json.containsKey("display")) {
                    // f.id、f.level、f.title ...
                    Label t = new Label(k, j, json.getString("display"));
                    ws.addCell(t);
                }
                k++;
            }

            Leaf lf = new Leaf();
            MyActionDb mad = new MyActionDb();
            j = 1;
            MacroCtlMgr mm = new MacroCtlMgr();
            while (ir.hasNext()) {
                WorkflowDb wfd = ir.next();
                String userName = wfd.getUserName();
                String userRealName = "";
                if (userName != null) {
                    userRealName = um.getUserDb(wfd.getUserName()).getRealName();
                }

                if (colLeaf.getType() == Leaf.TYPE_NONE) {
                    lf = lf.getLeaf(wfd.getTypeCode());
                    if (lf != null) {
                        fd = fd.getFormDb(lf.getFormCode());
                    } else {
                        lf = new Leaf();
                        LogUtil.getLog(getClass()).error(wfd.getTitle() + " 流程类型：" + wfd.getTypeCode() + " 不存在");
                    }
                }

                fdao = fdao.getFormDAOByCache(wfd.getId(), fd);
                if (fdao == null) {
                    fdao = new FormDAO();
                    LogUtil.getLog(getClass()).warn("记录: flowId=" + wfd.getId() + " 已被删除");
                } else {
                    k = 0;
                    com.alibaba.fastjson.JSONObject jo = getRow(wfd, fdao, colProps, um, userRealName, mad, mm, colLeaf, displayMode, "", desKey);
                    for (Object o : colProps) {
                        JSONObject json = (JSONObject) o;
                        String val;
                        if ("f.flow_level".equals(json.getString("field"))) {
                            val = WorkflowDb.getLevelDesc(jo.getIntValue(json.getString("field")));
                        } else if ("f.flow_level".equals(json.getString("name"))) {
                            val = WorkflowDb.getLevelDesc(jo.getIntValue(json.getString("name")));
                        } else {
                            if (json.containsKey("field")) {
                                val = jo.getString(json.getString("field"));
                            } else {
                                val = jo.getString(json.getString("name"));
                            }
                            if ("null".equals(val)) {
                                val = "";
                            }
                        }
                        Label t = new Label(k, j, val);
                        ws.addCell(t);
                        k++;
                    }
                }

                ExportExcelItem exportExcelItem = exportExcelCache.get(uid);
                if (exportExcelItem == null) {
                    LogUtil.getLog(getClass()).error("uid: " + uid + " is not found，it is may be expired.");
                } else {
                    exportExcelItem.setUid(uid);
                    exportExcelItem.setCurRow(j);
                    exportExcelCache.put(uid, exportExcelItem);
                }

                j++;
            }

            wwb.write();

            // 当异步导出Excel时
            if (sysProperties.isExportExcelAsync() && Global.getInstance().isUseCache()) {
                ExportExcelItem exportExcelItem = exportExcelCache.get(uid);
                if (exportExcelItem == null) {
                    LogUtil.getLog(getClass()).error("uid: " + uid + " is not found，it is may be expired.");
                } else {
                    exportExcelItem.setUid(uid);
                    exportExcelItem.setCurRow(exportExcelItem.getRows());
                    exportExcelCache.put(uid, exportExcelItem);

                    FormDb fdItem = new FormDb();
                    fdItem = fdItem.getFormDb(ConstUtil.FORM_EXPORT_EXCEL);
                    com.redmoon.oa.visual.FormDAO daoItem = new com.redmoon.oa.visual.FormDAO();
                    daoItem = daoItem.getFormDAOByCache(exportExcelItem.getId(), fdItem);
                    daoItem.setFieldValue("finish_time", new Date());
                    daoItem.save();
                }
            }
        } catch (Exception e) {
            LogUtil.getLog(getClass()).error(e);
        } finally {
            if (wwb != null) {
                try {
                    wwb.close();
                } catch (WriteException e) {
                    LogUtil.getLog(getClass()).error(e);
                }
            }
            if (wb != null) {
                wb.close();
            }

            if (os != null) {
                os.close();
            }
        }
    }

    @Override
    public com.alibaba.fastjson.JSONArray getMiniStateByTypeCode(String typeCodes) {
        String[] arr = StrUtil.split(typeCodes, ",");
        com.alibaba.fastjson.JSONArray ary = new com.alibaba.fastjson.JSONArray();
        for (String typeCode : arr) {
            JSONObject json = new JSONObject();
            int processCount = 0, finishedCount = 0;
            String sql = "select count(*) from flow where type_code=? and status=" + WorkflowDb.STATUS_STARTED;
            String sql2 = "select count(*) from flow where type_code=? and status=" + WorkflowDb.STATUS_FINISHED;
            JdbcTemplate jt = new JdbcTemplate();
            ResultIterator ri = null;
            try {
                ri = jt.executeQuery(sql, new Object[]{typeCode});
                if (ri.hasNext()) {
                    ResultRecord rr = ri.next();
                    processCount = rr.getInt(1);
                }
                ri = jt.executeQuery(sql2, new Object[]{typeCode});
                if (ri.hasNext()) {
                    ResultRecord rr = ri.next();
                    finishedCount = rr.getInt(1);
                }
            } catch (SQLException e) {
                LogUtil.getLog(getClass()).error(e);
            }
            json.put("processCount", processCount);
            json.put("finishedCount", finishedCount);
            ary.add(json);
        }
        return ary;
    }

    @Override
    public void distribute(int flowId, String roles, String users, String units, String paperTitle, boolean isMsg, boolean flowNotifyByEmail, boolean isSms, int actionId) throws ResKeyException {
        String myname = authUtil.getUserName();

        // 8.0起取消“流程是否可见”选项，默认可见，故消息中的action改为了查看流程 ACTION_FLOW_SHOW
        int isFlowDisplay = ParamUtil.getInt(request, "isFlowDisplay", 1);
        String[] ary = StrUtil.split(units, ",");
        int len = 0;
        if (ary != null) {
            len = ary.length;
        }

        WorkflowDb wf = new WorkflowDb();
        wf = wf.getWorkflowDb(flowId);
        if (StrUtil.isEmpty(paperTitle)) {
            paperTitle = wf.getTitle();
        }

        //发送邮件提醒
        SendMail sendmail = WorkflowDb.getSendMail();
        UserDb formUserDb = new UserDb();
        formUserDb = formUserDb.getUserDb(authUtil.getUserName());
        String fromNick = "";
        try {
            fromNick = MimeUtility.encodeText(formUserDb.getRealName());
        } catch (Exception e) {
            LogUtil.getLog(getClass()).error(e);
        }
        String fromEmail = Global.getEmail();
        fromNick = fromNick + "<" + fromEmail + ">";

        com.redmoon.oa.sso.Config ssoCfg = new com.redmoon.oa.sso.Config();

        boolean re = true;
        RoleDb rd = new RoleDb();
        DeptDb dd = new DeptDb();
        String fromUnit = authUtil.getUserUnitCode();
        dd = dd.getDeptDb(fromUnit);
        String unitName = dd.getName();

        // 从配置文件中得到收文角色
        PaperConfig pc = PaperConfig.getInstance();
        String swRoles = pc.getProperty("swRoles");
        String[] aryRole = StrUtil.split(swRoles, ",");
        int aryRoleLen = 0;
        if (aryRole != null) {
            aryRoleLen = aryRole.length;
        }
        RoleDb[] aryR = new RoleDb[aryRoleLen];
        // 取出收文角色
        for (int i = 0; i < aryRoleLen; i++) {
            aryR[i] = rd.getRoleDb(aryRole[i]);
        }
        for (int i = 0; i < len; i++) {
            PaperDistributeDb pdd = new PaperDistributeDb();
            String toUnit = ary[i];
            java.util.Date disDate = new java.util.Date();
            int isReaded = 0;
            int kind = PaperDistributeDb.KIND_UNIT;
            long paperId = SequenceManager.nextID(SequenceManager.OA_FLOW_PAPER_DISTRIBUTE);

            re = pdd.create(new JdbcTemplate(), new Object[]{paperId, paperTitle, flowId, fromUnit, toUnit, disDate, myname, isFlowDisplay, isReaded, kind, actionId, PaperDistributeDb.OP_TYPE_DIST});
            if (re) {
                for (int j = 0; j < aryRoleLen; j++) {
                    // 取出角色中的全部用户
                    for (UserDb userDb : aryR[j].getAllUserOfRole()) {
                        // 如果用户属于收文单位
                        if (userDb.getUnitCode().equals(toUnit)) {
                            // 消息提醒
                            String action;
                            if (isFlowDisplay == 0) {
                                action = "action=" + MessageDb.ACTION_PAPER_DISTRIBUTE + "|paperId=" + paperId;
                            } else {
                                action = "action=" + MessageDb.ACTION_FLOW_SHOW + "|flowId=" + flowId;
                            }

                            try {
                                String swNoticeTitle = pc.getProperty("swNoticeTitle");
                                swNoticeTitle = StrUtil.format(swNoticeTitle, new Object[]{unitName, paperTitle});
                                String swNoticeContent = pc.getProperty("swNoticeContent");
                                swNoticeContent = StrUtil.format(swNoticeContent, new Object[]{unitName, paperTitle, DateUtil.format(disDate, "yyyy-MM-dd")});

                                String tail = WorkflowMgr.getFormAbstractTable(wf);
                                swNoticeContent += tail;

                                MessageDb md = new MessageDb();
                                md.sendSysMsg(userDb.getName(), swNoticeTitle, swNoticeContent, action);

                                if (flowNotifyByEmail) {
                                    if (isFlowDisplay == 1) {
                                        String actionFlow = "userName=" + userDb.getName() + "|" +
                                                "flowId=" + flowId;
                                        actionFlow = ThreeDesUtil.encrypt2hex(ssoCfg.getKey(), actionFlow);
                                        UserSetupDb usd = new UserSetupDb(userDb.getName());
                                        swNoticeContent += "<BR />>>&nbsp;<a href='" +
                                                WorkflowUtil.getJumpUrl(WorkflowUtil.OP_FLOW_SHOW, actionFlow) +
                                                "' target='_blank'>" +
                                                ("en-US".equals(usd.getLocal()) ? "Click here to view" : "请点击此处查看") + "</a>";
                                    }
                                    sendmail.initMsg(userDb.getEmail(), fromNick, swNoticeTitle, swNoticeContent, true);
                                    sendmail.send();
                                    sendmail.clear();
                                }

                                String swPlanTitle = pc.getProperty("swPlanTitle");
                                swPlanTitle = StrUtil.format(swPlanTitle, new Object[]{paperTitle});
                                String swPlanContent = pc.getProperty("swPlanContent");
                                swPlanContent = StrUtil.format(swPlanContent, new Object[]{unitName, paperTitle, DateUtil.format(disDate, "yyyy-MM-dd")});

                                // 创建日程安排
                                PlanDb pd = new PlanDb();
                                pd.setTitle(swPlanTitle);
                                pd.setContent(swPlanContent);
                                pd.setMyDate(new Date());
                                pd.setEndDate(new Date());
                                pd.setActionData(String.valueOf(paperId));
                                pd.setActionType(PlanDb.ACTION_TYPE_PAPER_DISTRIBUTE);
                                pd.setUserName(userDb.getName());
                                pd.setRemind(false);
                                pd.setRemindBySMS(false);
                                pd.setRemindDate(new Date());
                                pd.create();
                            } catch (ErrMsgException ex2) {
                                ex2.printStackTrace();
                            }
                        }
                    }
                }
            }
        }

        UserDb toUserDb = new UserDb();
        ary = users.split(",");
        if (ary.length == 1 && "".equals(ary[0])) {
            len = 0;
        } else {
            len = ary.length;
        }
        ArrayList<String> arrList = new ArrayList<>(len);
        if (len > 0) {
            Collections.addAll(arrList, ary);
        }

        String[] arrRole = StrUtil.split(roles, ",");
        if (arrRole != null) {
            for (String code : arrRole) {
                List<User> list = roleService.getAllUserOfRole(code, true);
                for (User u : list) {
                    arrList.add(u.getName());
                }
            }
        }
        // 去重，删除重复项
        List<String> list = arrList.stream().distinct().collect(Collectors.toList());

        for (String userName : list) {
            PaperDistributeDb pdd = new PaperDistributeDb();
            java.util.Date disDate = new java.util.Date();
            int isReaded = 0;
            int kind = PaperDistributeDb.KIND_USER;
            long paperId = SequenceManager.nextID(SequenceManager.OA_FLOW_PAPER_DISTRIBUTE);
            re = pdd.create(new JdbcTemplate(), new Object[]{paperId, paperTitle, flowId, fromUnit, userName, disDate, myname, isFlowDisplay, isReaded, kind, actionId, PaperDistributeDb.OP_TYPE_DIST});
            if (re) {
                // 消息提醒
                String action;
                if (isFlowDisplay == 0) {
                    action = "action=" + MessageDb.ACTION_PAPER_DISTRIBUTE + "|paperId=" + paperId;
                } else {
                    action = "action=" + MessageDb.ACTION_FLOW_SHOW + "|flowId=" + flowId;
                }
                try {
                    String swNoticeTitle = pc.getProperty("swNoticeTitle");
                    swNoticeTitle = StrUtil.format(swNoticeTitle, new Object[]{unitName, paperTitle});
                    String swNoticeContent = pc.getProperty("swNoticeContent");
                    swNoticeContent = StrUtil.format(swNoticeContent, new Object[]{unitName, paperTitle, DateUtil.format(disDate, "yyyy-MM-dd")});
                    MessageDb md = new MessageDb();
                    md.sendSysMsg(userName, swNoticeTitle, swNoticeContent, action);

                    if (flowNotifyByEmail) {
                        toUserDb = toUserDb.getUserDb(userName);
                        if (isFlowDisplay == 1) {
                            String actionFlow = "userName=" + toUserDb.getName() + "|" + "flowId=" + flowId;
                            actionFlow = ThreeDesUtil.encrypt2hex(ssoCfg.getKey(), actionFlow);
                            UserSetupDb usd = new UserSetupDb(toUserDb.getName());
                            swNoticeContent += "<BR />>>&nbsp;<a href='" +
                                    WorkflowUtil.getJumpUrl(WorkflowUtil.OP_FLOW_SHOW, actionFlow) +
                                    "' target='_blank'>" +
                                    ("en-US".equals(usd.getLocal()) ? "Click here to view" : "请点击此处查看") + "</a>";
                        }
                        sendmail.initMsg(toUserDb.getEmail(), fromNick, swNoticeTitle, swNoticeContent, true);
                        sendmail.send();
                        sendmail.clear();
                    }

                    String swPlanTitle = pc.getProperty("swPlanTitle");
                    swPlanTitle = StrUtil.format(swPlanTitle, new Object[]{paperTitle});
                    String swPlanContent = pc.getProperty("swPlanContent");
                    swPlanContent = StrUtil.format(swPlanContent, new Object[]{unitName, paperTitle, DateUtil.format(disDate, "yyyy-MM-dd")});

                    // 创建日程安排
                    PlanDb pd = new PlanDb();
                    pd.setTitle(swPlanTitle);
                    pd.setContent(swPlanContent);
                    pd.setMyDate(new java.util.Date());
                    pd.setEndDate(new java.util.Date());
                    pd.setActionData(String.valueOf(paperId));
                    pd.setActionType(PlanDb.ACTION_TYPE_PAPER_DISTRIBUTE);
                    pd.setUserName(userName);
                    pd.setRemind(false);
                    pd.setRemindBySMS(false);
                    pd.setRemindDate(new java.util.Date());
                    pd.create();
                } catch (ErrMsgException ex2) {
                    LogUtil.getLog(getClass()).error(ex2);
                }
            }
        }

        // 生成PDF
        /*int doc_id = wf.getDocId();
        com.redmoon.oa.flow.DocumentMgr dm = new com.redmoon.oa.flow.DocumentMgr();
        com.redmoon.oa.flow.Document doc = dm.getDocument(doc_id);
        if (doc == null) {
            throw new ErrMsgException("流程文档不存在");
        }
        java.util.Vector<IAttachment> attachments = doc.getAttachments(1);
        for (IAttachment att : attachments) {
            String ext = StrUtil.getFileExt(att.getName());
            if ("doc".equals(ext) || "docx".equals(ext)) {
                ;
            } else {
                continue;
            }

            String fileName = att.getDiskName();
            String fName = fileName.substring(0, fileName.lastIndexOf("."));
            fileName = fName + ".pdf";

            String fileDiskPath = Global.getRealPath() + att.getVisualPath() + "/" + att.getDiskName();
            String pdfPath = Global.getRealPath() + att.getVisualPath() + "/" + fileName;
            try {
                com.redmoon.oa.util.PDFConverter.convert2pdf(fileDiskPath, pdfPath);
            } catch (Exception e) {
                LogUtil.getLog(getClass()).error(e);
            }
        }*/
    }

    @Async("threadPoolTaskExecutor")
    @Override
    public void convertToPdfAsync(int flowId, Attachment att, String userName, String tmpAttchFilePath) {
        convertToPdf(flowId, att, userName, tmpAttchFilePath);
    }

    @Override
    public Attachment convertToPdf(int flowId, Attachment att, String userName, String tmpAttchFilePath) {
        String diskName = att.getDiskName();
        String pdfName = diskName.substring(0, diskName.lastIndexOf(".")) + ".pdf";
        String attPath;
        if (tmpAttchFilePath == null) {
            attPath = Global.getRealPath() + att.getVisualPath() + "/" + att.getDiskName();
        } else {
            attPath = tmpAttchFilePath;
        }
        String pdfPath = Global.getRealPath() + att.getVisualPath() + "/" + pdfName;
        Attachment attachment = new Attachment();
        try {
            Word2PDF.officeToPdf(attPath, pdfPath);

            boolean isNew = true;
            // 当用在generateRedDocument中时，att可能并非从数据库中加载，而只是个临时变量，此时isLoaded()为false
            if (att.isLoaded()) {
                Attachment oldAtt = attachment.getAttachmentBySourceId(att.getId());
                // 如果原来已生成过pdf文件则更新
                if (oldAtt != null) {
                    isNew = false;

                    // 不能删除，只能更新，否则会将套红后生成的pdf也删除了
                    // oldAtt.del();
                    File file = new File(pdfPath);
                    oldAtt.setFlowId(flowId);
                    oldAtt.setPageNum(1);
                    oldAtt.setFieldName(att.getFieldName());
                    oldAtt.setVisualPath(att.getVisualPath());
                    oldAtt.setDiskName(pdfName);
                    oldAtt.setSize(file.length());
                    oldAtt.setName(att.getName().substring(0, att.getName().lastIndexOf(".")) + ".pdf");
                    oldAtt.setDocId(att.getDocId());
                    oldAtt.setCreator(userName);
                    oldAtt.setCreateDate(new Date());
                    oldAtt.setOrders(att.getOrders() + 1);
                    oldAtt.save();
                }
            }

            if (isNew){
                // 生成pdf文件的附件记录
                File file = new File(pdfPath);
                attachment.setFlowId(flowId);
                attachment.setPageNum(1);
                attachment.setFieldName(att.getFieldName());
                attachment.setVisualPath(att.getVisualPath());
                attachment.setDiskName(pdfName);
                attachment.setSize(file.length());
                attachment.setName(att.getName().substring(0, att.getName().lastIndexOf(".")) + ".pdf");
                attachment.setDocId(att.getDocId());
                attachment.setCreator(userName);
                attachment.setCreateDate(new Date());
                attachment.setOrders(att.getOrders() + 1);
                attachment.setSourceId(att.getId());
                attachment.create();
            }

            // 删除临时文件
            if (tmpAttchFilePath != null) {
                FileUtil.del(tmpAttchFilePath);
            }
        } catch (Exception e) {
            throw new ErrMsgException("转换PDF失败：" + e.getMessage());
        }
        return attachment;
    }

    @Override
    public com.redmoon.oa.visual.Attachment convertToPdf(long id, com.redmoon.oa.visual.Attachment att, String userName, String tmpAttchFilePath) {
        String diskName = att.getDiskName();
        String pdfName = diskName.substring(0, diskName.lastIndexOf(".")) + ".pdf";
        String attPath;
        if (tmpAttchFilePath == null) {
            attPath = Global.getRealPath() + att.getVisualPath() + "/" + att.getDiskName();
        } else {
            attPath = tmpAttchFilePath;
        }
        String pdfPath = Global.getRealPath() + att.getVisualPath() + "/" + pdfName;
        com.redmoon.oa.visual.Attachment attachment = new com.redmoon.oa.visual.Attachment();
        try {
            Word2PDF.officeToPdf(attPath, pdfPath);

            boolean isNew = true;
            // 当用在generateRedDocument中时，att可能并非从数据库中加载，而只是个临时变量，此时isLoaded()为false
            if (att.isLoaded()) {
                com.redmoon.oa.visual.Attachment oldAtt = attachment.getAttachmentBySourceId(att.getId());
                // 如果原来已生成过pdf文件则更新
                if (oldAtt != null) {
                    isNew = false;

                    // 不能删除，只能更新，否则会将套红后生成的pdf也删除了
                    // oldAtt.del();
                    File file = new File(pdfPath);
                    oldAtt.setVisualId(id);
                    oldAtt.setFieldName(att.getFieldName());
                    oldAtt.setVisualPath(att.getVisualPath());
                    oldAtt.setDiskName(pdfName);
                    oldAtt.setSize(file.length());
                    oldAtt.setName(att.getName().substring(0, att.getName().lastIndexOf(".")) + ".pdf");
                    oldAtt.setCreator(userName);
                    oldAtt.setCreateDate(new Date());
                    oldAtt.setOrders(att.getOrders() + 1);
                    oldAtt.save();
                }
            }

            if (isNew){
                // 生成pdf文件的附件记录
                File file = new File(pdfPath);
                attachment.setVisualId(id);
                attachment.setFieldName(att.getFieldName());
                attachment.setVisualPath(att.getVisualPath());
                attachment.setDiskName(pdfName);
                attachment.setSize(file.length());
                attachment.setName(att.getName().substring(0, att.getName().lastIndexOf(".")) + ".pdf");
                attachment.setCreator(userName);
                attachment.setCreateDate(new Date());
                attachment.setOrders(att.getOrders() + 1);
                attachment.setSourceId(att.getId());
                attachment.create();
            }

            // 删除临时文件
            if (tmpAttchFilePath != null) {
                FileUtil.del(tmpAttchFilePath);
            }
        } catch (Exception e) {
            throw new ErrMsgException("转换PDF失败：" + e.getMessage());
        }
        return attachment;
    }

    //ofd 转 pdf 转 html
    @Async("threadPoolTaskExecutor")
    @Override
    public void convertOfdToPfdToHtmlAsync(int flowId, Attachment att, String userName, String diskName,String visualPath) {
        convertOfdToPfdToHtml(flowId, att, userName,diskName,visualPath);
    }

    /**
     * ofd 转 pdf 再转 html, 如果直接调用 OfdUtil.ofdToHtml 会报渲染错误
     * @param flowId
     * @param att
     * @param userName
     * @return
     */
    @Override
    public void convertOfdToPfdToHtml(int flowId, Attachment att, String userName, String diskName,String visualPath) {
        if(StrUtil.isEmpty(diskName)){
            diskName = att.getDiskName();
        }
        String pdfName = diskName.substring(0, diskName.lastIndexOf(".")) + ".pdf";
        String htmlName = diskName.substring(0, diskName.lastIndexOf(".")) + ".html";
        String attPath = "";
        String pdfPath = "";
        String htmlPath = "";
        if(StrUtil.isEmpty(visualPath)){
            attPath = Global.getRealPath() + att.getVisualPath() + "/" + att.getDiskName();
            pdfPath = Global.getRealPath() + att.getVisualPath() + "/" + pdfName;
            htmlPath = Global.getRealPath() + att.getVisualPath() + "/" + htmlName;
        }else{
            attPath = Global.getRealPath() + visualPath + "/" + diskName;
            pdfPath = Global.getRealPath() + visualPath + "/" + pdfName;
            htmlPath = Global.getRealPath() + visualPath + "/" + htmlName;
        }

        File pdf = new File(pdfPath);
        File html = new File(htmlPath);
        if(!html.exists()) {
            if (!pdf.exists()) {
                //如果pdf不存在
                OfdUtil.ofdToPdf(attPath, pdfPath);
                Ofd2Html.createPreviewHTML(pdfPath);
                try {
                    FileUtil.del(pdfPath);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }else {
                //存在pdf
                Ofd2Html.createPreviewHTML(pdfPath);
                try {
                    FileUtil.del(pdfPath);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //ofd转html
    @Async("threadPoolTaskExecutor")
    @Override
    public void convertOfdToHtmlAsync(String ofdPath,String htmlPath) {
        OfdUtil.ofdToHtml(ofdPath, htmlPath);
    }

    //docx转ofd
    @Async("threadPoolTaskExecutor")
    @Override
    public void convertToOfdAsync(int flowId, Attachment att, String userName,String diskName,String visualPath) {
        convertToOfd(flowId, att, userName,diskName,visualPath);
    }

    /**
     * docx 转 ofd
     * @param flowId
     * @param att
     * @param userName
     * @return
     */
    @Override
    public void convertToOfd(int flowId, Attachment att, String userName,String diskName,String visualPath) {
        if(StrUtil.isEmpty(diskName)){
            diskName = att.getDiskName();
        }
        String pdfName = diskName.substring(0, diskName.lastIndexOf(".")) + ".pdf";
        String ofdName = diskName.substring(0, diskName.lastIndexOf(".")) + ".ofd";
        String attPath = "";
        String pdfPath = "";
        String ofdPath = "";
        if(StrUtil.isEmpty(visualPath)) {
             attPath = Global.getRealPath() + att.getVisualPath() + "/" + att.getDiskName();
             pdfPath = Global.getRealPath() + att.getVisualPath() + "/" + pdfName;
             ofdPath = Global.getRealPath() + att.getVisualPath() + "/" + ofdName;
        }else{
            attPath = Global.getRealPath() + visualPath + "/" + diskName;
            pdfPath = Global.getRealPath() + visualPath + "/" + pdfName;
            ofdPath = Global.getRealPath() + visualPath + "/" + ofdName;
        }
        com.redmoon.oa.Config cfg = com.redmoon.oa.Config.getInstance();

        File pdf = new File(pdfPath);
        File ofd = new File(ofdPath);
        if (!ofd.exists()) {
            if (!pdf.exists()) {
                // 如果pdf不存在
                Word2PDF.officeToPdf(attPath, pdfPath);
                OfdUtil.convertToOfdByStream(pdfPath, ofdPath);
                //判断一下系统是否允许word转pdf，此时pdf不能删
                if(!cfg.getBooleanProperty("canConvertToPDF")){
                    try {
                        FileUtil.del(pdfPath);
                    } catch (IOException e) {
                        LogUtil.getLog(getClass()).error(e);
                    }
                }
            } else {
                // 存在pdf
                OfdUtil.convertToOfdByStream(pdfPath, ofdPath);
                //判断一下系统是否允许word转pdf，此时pdf不能删
                if(!cfg.getBooleanProperty("canConvertToPDF")){
                    try {
                        FileUtil.del(pdfPath);
                    } catch (IOException e) {
                        LogUtil.getLog(getClass()).error(e);
                    }
                }
            }
        }
    }

    @Override
    public void onFlowInited(int flowId, FormDAO fdaoFlow) {
        String op = ParamUtil.get(request, "op");
        // 如果是来自于智能模块的操作列，则映射字段值
        if ("opLinkFlow".equals(op)) {
            long moduleId = ParamUtil.getLong(request, "moduleId", -1);
            if (moduleId != -1) {
                String moduleCode = ParamUtil.get(request, "moduleCode");
                String linkName = ParamUtil.get(request, "btnId");

                int i = 0;
                ModuleSetupDb msd = new ModuleSetupDb();
                msd = msd.getModuleSetupDb(moduleCode);
                FormDb fdModule = new FormDb();
                fdModule = fdModule.getFormDb(msd.getString("form_code"));
                com.redmoon.oa.visual.FormDAO fdaoModule = new com.redmoon.oa.visual.FormDAO();
                fdaoModule = fdaoModule.getFormDAOByCache(moduleId, fdModule);

                String op_link_name = StrUtil.getNullStr(msd.getString("op_link_name"));
                String[] linkNames = StrUtil.split(op_link_name, ",");
                String op_link_url = StrUtil.getNullStr(msd.getString("op_link_url"));
                String[] linkHrefs = StrUtil.split(op_link_url, ",");

                // 取操作列的配置，映射字段值
                int len = 0;
                if (linkNames != null) {
                    len = linkNames.length;
                }
                for (i = 0; i < len; i++) {
                    String lkName = linkNames[i];
                    if (lkName.equals(linkName)) {
                        String url = StrUtil.decodeJSON(linkHrefs[i]);
                        JSONObject json = JSONObject.parseObject(url);
                        String maps = json.getString("params");

                        if (!StrUtil.isEmpty(maps)) {
                            try {
                                // 映射字段值
                                ModuleUtil.doMapOnFlow(request, fdaoModule, fdaoFlow, maps);
                            } catch (ErrMsgException e) {
                                LogUtil.getLog(getClass()).error(e);
                            } catch (SQLException throwables) {
                                throwables.printStackTrace();
                            }
                        }
                    }
                }
            }
        } else if ("opBtnFlow".equals(op)) {
            String btnFieldName;
            int byFlowId;
            if (ParamUtil.isMobile(request)) {
                String btnParams = ParamUtil.get(request, "btnParams");
                btnParams = btnParams.replaceAll("\\\"", "\"");
                JSONObject json = JSONObject.parseObject(btnParams);
                btnFieldName = json.getString("btnFieldName");
                byFlowId = json.getIntValue("flowId");
            } else {
                btnFieldName = ParamUtil.get(request, "btnFieldName");
                byFlowId = ParamUtil.getInt(request, "flowId", -1);
            }
            if (!StrUtil.isEmpty(btnFieldName)) {
                WorkflowDb wf = new WorkflowDb();
                wf = wf.getWorkflowDb(byFlowId);
                Leaf lf = new Leaf();
                lf = lf.getLeaf(wf.getTypeCode());
                FormDb byFd = new FormDb();
                byFd = byFd.getFormDb(lf.getFormCode());
                FormField ff = byFd.getFormField(btnFieldName);
                if (ff != null) {
                    /*String desc = StrUtil.decodeJSON(ff.getDescription());
                    desc = desc.replaceAll("\\\\\\\\\"", "\\\\\"");*/
                    JSONObject jsonBtn = JSONObject.parseObject(ff.getDescription());
                    FormDAO byFdao = new FormDAO();
                    byFdao = byFdao.getFormDAOByCache(byFlowId, byFd);

                    String href = jsonBtn.getString("href");
                    href = StrUtil.decodeJSON(href);
                    com.alibaba.fastjson.JSONObject jsonHref = com.alibaba.fastjson.JSONObject.parseObject(href);
                    String maps = jsonHref.getString("params");
                    if (!StrUtil.isEmpty(maps)) {
                        try {
                            // 映射字段值
                            ModuleUtil.doMapOnFlow(request, byFdao, fdaoFlow, maps);
                        } catch (ErrMsgException e) {
                            LogUtil.getLog(getClass()).error(e);
                        } catch (SQLException throwables) {
                            throwables.printStackTrace();
                        }

                        // 将流程置为未开始状态，使在待办列表可见
                        wf = wf.getWorkflowDb(flowId);
                        wf.setStatus(WorkflowDb.STATUS_NOT_STARTED);
                        wf.save();
                    }
                } else {
                    LogUtil.getLog(getClass()).error("表单" + fdaoFlow.getFormDb().getName() + " 中未找到按钮: " + btnFieldName);
                }
            } else {
                // 编辑页、详情页中的按钮
                long moduleId = ParamUtil.getLong(request, "moduleId", -1);
                if (moduleId != -1) {
                    String moduleCode = ParamUtil.get(request, "moduleCode");
                    String btnId = ParamUtil.get(request, "btnId");
                    String pageType = ParamUtil.get(request, "pageType");

                    ModuleSetupDb msd = new ModuleSetupDb();
                    msd = msd.getModuleSetupDb(moduleCode);
                    FormDb fdModule = new FormDb();
                    fdModule = fdModule.getFormDb(msd.getString("form_code"));
                    com.redmoon.oa.visual.FormDAO fdaoModule = new com.redmoon.oa.visual.FormDAO();
                    fdaoModule = fdaoModule.getFormDAOByCache(moduleId, fdModule);

                    com.alibaba.fastjson.JSONArray ary = msd.getBtnProps(pageType);
                    if (ary != null) {
                        for (int i = 0; i < ary.size(); i++) {
                            com.alibaba.fastjson.JSONObject jsonBtn = ary.getJSONObject(i);
                            if (!btnId.equals(jsonBtn.getString("id"))) {
                                continue;
                            }

                            String href = jsonBtn.getString("href");
                            href = StrUtil.decodeJSON(href);
                            com.alibaba.fastjson.JSONObject jsonHref = com.alibaba.fastjson.JSONObject.parseObject(href);
                            String maps = jsonHref.getString("params");

                            if (!StrUtil.isEmpty(maps)) {
                                try {
                                    // 映射字段值
                                    ModuleUtil.doMapOnFlow(request, fdaoModule, fdaoFlow, maps);
                                } catch (ErrMsgException e) {
                                    LogUtil.getLog(getClass()).error(e);
                                } catch (SQLException throwables) {
                                    throwables.printStackTrace();
                                }
                            }
                        }
                    } else {
                        DebugUtil.e(getClass(), "opBtnFlow", "未找到btnProps");
                    }
                }
            }
        } else if ("handlePaper".equals(op)) {
            // 收文处理
            int paperFlowId = ParamUtil.getInt(request, "paperFlowId", -1);
            if (paperFlowId != -1) {
                WorkflowDb wf = new WorkflowDb();
                wf = wf.getWorkflowDb(paperFlowId);
                int doc_id = wf.getDocId();
                DocumentMgr dm = new DocumentMgr();
                Document doc = dm.getDocument(doc_id);
                if (doc != null) {
                    FormDAO fdao = new FormDAO();
                    String visualPath = fdao.getVisualPath();
                    java.util.Vector<IAttachment> attachments = doc.getAttachments(1);
                    for (IAttachment am : attachments) {
                        String randName = FileUpload.getRandName();
                        String ext = StrUtil.getFileExt(am.getName());

                        String diskName = am.getDiskName();
                        String fileName = am.getName();
                        // 不再复制pdf的文件，而直接复制原文件
                        if (false && ("doc".equals(ext) || "docx".equals(ext))) {
                            randName += ".pdf";
                            int p = diskName.lastIndexOf(".");
                            diskName = diskName.substring(0, p);
                            diskName += ".pdf";

                            p = fileName.lastIndexOf(".");
                            fileName = fileName.substring(0, p);
                            fileName += ".pdf";
                        } else {
                            randName += "." + ext;
                        }

                        fileService.write(Global.getRealPath() + am.getVisualPath() + "/" + diskName, visualPath, randName);

                        WorkflowDb mywf = new WorkflowDb();
                        mywf = mywf.getWorkflowDb(flowId);
                        int myDocId = mywf.getDocId();

                        Attachment att = new Attachment();
                        att.setDocId(myDocId);
                        att.setName(fileName);
                        att.setDiskName(randName);
                        att.setVisualPath(visualPath);
                        att.setPageNum(1);
                        att.setOrders(am.getOrders());
                        att.setFieldName("");
                        att.setCreator(authUtil.getUserName());
                        att.setSize(am.getSize());
                        att.setFlowId(flowId);
                        att.create();
                    }
                }
            }
        }
    }

    /**
     * 取得流程中所设的所有节点上的表单
     * @param wf
     * @return
     */
    @Override
    public List<String> getActionForms(WorkflowDb wf) {
        List<String> forms = new ArrayList<>();
        org.json.JSONObject flowJsonObject;
        try {
            /** 有时会出现这样的情况，得替换掉\comma，否则会报含有转义字符错误
             * "ActionItem2": {
             * 	    "name": "ActionItem2",
             * 	    "value": "{default\comma1\comma0\comma\commafalse\comma0\comma0\comma0\comma0\comma0}"
             *  },
             */
            String flowJson = wf.getFlowJson();
            flowJson = flowJson.replaceAll("\\\\comma", ",");
            flowJsonObject = new org.json.JSONObject(flowJson);

            try {
                org.json.JSONObject states = flowJsonObject.getJSONObject("states");
                Iterator irJson = states.keys();
                while (irJson.hasNext()) {
                    String key = (String) irJson.next();
                    org.json.JSONObject jsonAction = states.getJSONObject(key);
                    org.json.JSONObject props = jsonAction.getJSONObject("props");
                    if (props.has("ActionForm")) {
                        org.json.JSONObject json = props.getJSONObject("ActionForm");
                        if (!forms.contains(json.getString("value"))) {
                            if (!StrUtil.isEmpty(json.getString("value"))) {
                                forms.add(json.getString("value"));
                            }
                        }
                    }
                }
            } catch (JSONException e) {
                LogUtil.getLog(getClass()).error(e);
            }
        } catch (JSONException e) {
            LogUtil.getLog(getClass()).error(e);
        }
        return forms;
    }

    /**
     * 判断流程的节点中是否含有审批人类型为发起者选人
     * @param wf
     * @return
     */
    public boolean hasApproverSelectByStarter(WorkflowDb wf) {
        org.json.JSONObject flowJsonObject;
        try {
            /** 有时会出现这样的情况，得替换掉\comma，否则会报含有转义字符错误
             * "ActionItem2": {
             * 	    "name": "ActionItem2",
             * 	    "value": "{default\comma1\comma0\comma\commafalse\comma0\comma0\comma0\comma0\comma0}"
             *  },
             */
            String flowJson = wf.getFlowJson();
            if (flowJson != null) {
                flowJson = flowJson.replaceAll("\\\\comma", ",");
                flowJsonObject = new org.json.JSONObject(flowJson);
            } else {
                LogUtil.getLog(getClass()).warn("流程: " + wf.getId() + " flowJson is null");
                return false;
            }
        } catch (JSONException e) {
            LogUtil.getLog(getClass()).error(e);
            flowJsonObject = new org.json.JSONObject();
        }

        try {
            org.json.JSONObject states = flowJsonObject.getJSONObject("states");
            Iterator<String> irJson = states.keys();
            while (irJson.hasNext()) {
                String key = irJson.next();
                org.json.JSONObject jsonAction = states.getJSONObject(key);
                org.json.JSONObject props = jsonAction.getJSONObject("props");
                if (props.has("ActionApproverType")) {
                    org.json.JSONObject json = props.getJSONObject("ActionApproverType");
                    String value = json.getString("value");
                    if ("1".equals(value)) {
                        return true;
                    }
                }
            }
        } catch (JSONException e) {
            LogUtil.getLog(getClass()).error(e);
        }
        return false;
    }

    @Override
    public String getActionPropByFlowJson(WorkflowDb wf, WorkflowActionDb wad, String prop) {
        String text = "";
        org.json.JSONObject flowJsonObject;
        try {
            /** 有时会出现这样的情况，得替换掉\comma，否则会报含有转义字符错误
             * "ActionItem2": {
             * 	    "name": "ActionItem2",
             * 	    "value": "{default\comma1\comma0\comma\commafalse\comma0\comma0\comma0\comma0\comma0}"
             *  },
             */
            String flowJson = wf.getFlowJson();
            if (flowJson != null) {
                flowJson = flowJson.replaceAll("\\\\comma", ",");
                flowJsonObject = new org.json.JSONObject(flowJson);
            } else {
                LogUtil.getLog(getClass()).warn("流程: " + wf.getId() + " flowJson is null");
                return text;
            }
        } catch (JSONException e) {
            LogUtil.getLog(getClass()).error(e);
            flowJsonObject = new org.json.JSONObject();
        }

        try {
            org.json.JSONObject states = flowJsonObject.getJSONObject("states");
            Iterator<String> irJson = states.keys();
            while (irJson.hasNext()) {
                String key = irJson.next();
                if (wad.getInternalName().equals(key)) {
                    org.json.JSONObject jsonAction = states.getJSONObject(key);
                    org.json.JSONObject props = jsonAction.getJSONObject("props");
                    if (props.has(prop)) {
                        org.json.JSONObject json = props.getJSONObject(prop);
                        text = json.getString("value");
                    }
                    break;
                }
            }
        } catch (JSONException e) {
            LogUtil.getLog(getClass()).error(e);
        }
        return text;
    }

    /**
     * 加入水印
     * @param myActionId
     * @param att
     */
    @Override
    public void addWatermark(long myActionId, Attachment att) {
        MyActionDb mad = new MyActionDb();
        mad = mad.getMyActionDb(myActionId);

        WorkflowDb wf = new WorkflowDb();
        wf = wf.getWorkflowDb((int) mad.getFlowId());
        long actionId = mad.getActionId();
        WorkflowActionDb wad = new WorkflowActionDb();
        wad = wad.getWorkflowActionDb((int) actionId);
        com.redmoon.oa.flow.FormDAO fdao = new com.redmoon.oa.flow.FormDAO();
        Leaf lf = new Leaf();
        lf = lf.getLeaf(wf.getTypeCode());
        FormDb fd = new FormDb();
        fd = fd.getFormDb(lf.getFormCode());
        fdao = fdao.getFormDAO((int) mad.getFlowId(), fd);

        String ext = StrUtil.getFileExt(att.getDiskName());
        if (wad.canWatermark() && ("pdf".equals(ext) || "docx".equals(ext))) {
            String text = "内部资料";
            text = getActionPropByFlowJson(wf, wad, "ActionWatermarkText");
            text = ModuleUtil.parseField(fdao, text);

            String localPath = Global.getRealPath() + att.getVisualPath() + "/" + att.getDiskName();
            if (sysProperties.isObjStoreEnabled()) {
                try {
                    String tmpLocalDiskName = FileUpload.getRandName() + "." + StrUtil.getFileExt(att.getDiskName());
                    fileService.copyToLocalFile(tmpLocalDiskName, att.getVisualPath(), att.getDiskName(), Global.getRealPath() + att.getVisualPath());
                    localPath = Global.getRealPath() + att.getVisualPath() + "/" + tmpLocalDiskName;
                } catch (IOException e) {
                    LogUtil.getLog(getClass()).error(e);
                }
            }

            String pathTmp = Global.getRealPath() + att.getVisualPath() + "/" + att.getDiskName() + "_tmp";
            if ("docx".equals(ext)) {
                WordWatermarkUtil.waterMarkDocXDocument(localPath, pathTmp, text);
            } else {
                PdfUtil.addWatermark(localPath, pathTmp, text);
            }
            File f = new File(pathTmp);
            cn.hutool.core.io.FileUtil.rename(f, localPath, false, true);
            if (sysProperties.isObjStoreEnabled()) {
                fileService.write(localPath, att.getVisualPath(), att.getDiskName());
            }
            // 删除用于加水印的临时文件
            f.delete();

            if (sysProperties.isObjStoreEnabled()) {
                File file = new File(localPath);
                if (file.exists()) {
                    file.delete();
                }
            }
        }
    }

    /**
     * 取得节点上对应的dao（节点上可能设置了表单）
     * @param wf
     * @param wa
     * @return
     * @throws ErrMsgException
     */
    @Override
    public FormDAO getFormDAOByAction(WorkflowDb wf, WorkflowActionDb wa) throws ErrMsgException {
        FormDb fd = new FormDb();
        String actionForm = getActionPropByFlowJson(wf, wa, "ActionForm");
        // 如果节点上采用了其它表单
        if (!StrUtil.isEmpty(actionForm)) {
            fd = fd.getFormDb(actionForm);
        } else {
            Leaf lf = new Leaf();
            lf = lf.getLeaf(wf.getTypeCode());
            fd = fd.getFormDb(lf.getFormCode());
        }
        if (!fd.isLoaded()) {
            throw new ErrMsgException("表单: " + (StrUtil.isEmpty(actionForm) ? fd.getCode() : actionForm) + " 不存在");
        }

        FormDAO fdao;
        // if (!StrUtil.isEmpty(actionForm)) {
            // 如果模式为创建记录，则检查当前处理者是否已创建记录
            String actionFormMode = getActionPropByFlowJson(wf, wa, "ActionFormMode");
            if (ConstUtil.ACTION_FORM_MODE_CREATE.equals(actionFormMode)) {
                Map<String, Object> params = new HashMap<>();
                params.put("flowId", wf.getId());
                params.put("cws_creator", authUtil.getUserName());
                FormDAO dao = new FormDAO();
                List<FormDAO> list = dao.selectListById(fd.getCode(), params);
                if (list.size() > 0) {
                    fdao = list.get(0);
                } else {
                    // 如果当前处理者尚未创建记录，则创建一条新记录
                    fdao = wf.createFormDao(wf.getId(), fd);
                    flowFormDaoCache.removeFromCache(fdao);
                }
            } else {
                // 如果模式为修改记录
                FormDAO dao = new FormDAO();
                fdao = dao.getFormDAOByCache(wf.getId(), fd);
                if (fdao == null) {
                    // 如果未取到，则创建一条新记录
                    fdao = wf.createFormDao(wf.getId(), fd);
                    flowFormDaoCache.removeFromCache(fdao);
                }
            }
        /*} else {
            fdao = fdao.getFormDAOByCache(wf.getId(), fd);
        }*/
        return fdao;
    }

    public Object[] parseAtUsers(String content) {
        // 解析 @
        StringBuffer sbuf = new StringBuffer();
        List<String> list = new ArrayList<>();
        String patternStr = "@(.*?)[&|<| |　]"; // 注意全角空格
        Pattern pattern = Pattern.compile(patternStr,
                Pattern.DOTALL | Pattern.CASE_INSENSITIVE);
        // 后面加个空格，以免最后一个@*** 无法解析
        Matcher matcher = pattern.matcher(content + " ");
        boolean ret = matcher.find();
        while (ret) {
            String u = matcher.group(1);
            User user = userService.getUserByLoginName(u);
            if (user == null) {
                LogUtil.getLog(getClass()).error("@" + u + " 用户不存在");
                break;
            }

            // 避免重复
            if (!list.contains(user.getName())) {
                list.add(user.getName());
            }
            if (!user.getRealName().equals(u)) {
                matcher.appendReplacement(sbuf, "@" + user.getRealName() + " ");
            }
            ret = matcher.find();
        }
        matcher.appendTail(sbuf);
        content = sbuf.toString();

        Object[] ary = new Object[2];
        ary[0] = list;
        ary[1] = content;
        return ary;
    }
}
